Skip to content

Commit c8b8529

Browse files
Add graphql schema for service context proj (#2302)
1 parent 6eab3cb commit c8b8529

File tree

19 files changed

+300
-53
lines changed

19 files changed

+300
-53
lines changed

assets/src/generated/graphql.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1141,6 +1141,8 @@ export type Cluster = {
11411141
nodeStatistics?: Maybe<Array<Maybe<NodeStatistic>>>;
11421142
/** list cached nodes for a cluster, this can be stale up to 5m */
11431143
nodes?: Maybe<Array<Maybe<Node>>>;
1144+
/** A pod-level set of utilization metrics exceeding our noisy threshold */
1145+
noisyNeighbors?: Maybe<UtilizationHeatMap>;
11441146
/** the object store connection bound to this cluster for backup/restore */
11451147
objectStore?: Maybe<ObjectStore>;
11461148
/** a high level description of the setup of common resources in a cluster */
@@ -9156,13 +9158,17 @@ export type ServiceContext = {
91569158
id: Scalars['ID']['output'];
91579159
insertedAt?: Maybe<Scalars['DateTime']['output']>;
91589160
name: Scalars['String']['output'];
9161+
/** the project this context belongs to */
9162+
project?: Maybe<Project>;
91599163
secrets?: Maybe<Array<Maybe<ServiceConfiguration>>>;
91609164
updatedAt?: Maybe<Scalars['DateTime']['output']>;
91619165
};
91629166

91639167
/** A reusable configuration context, useful for plumbing data from external tools like terraform/pulumi/etc */
91649168
export type ServiceContextAttributes = {
91659169
configuration?: InputMaybe<Scalars['Json']['input']>;
9170+
/** the project this context belongs to */
9171+
projectId?: InputMaybe<Scalars['ID']['input']>;
91669172
secrets?: InputMaybe<Array<InputMaybe<ConfigAttributes>>>;
91679173
};
91689174

charts/controller/crds/deployments.plural.sh_servicecontexts.yaml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,51 @@ spec:
5353
description: the name of this service, if not provided ServiceContext's
5454
own name from ServiceContext.ObjectMeta will be used.
5555
type: string
56+
projectRef:
57+
description: |-
58+
ProjectRef references project this service context belongs to.
59+
If not provided, it will use the default project.
60+
properties:
61+
apiVersion:
62+
description: API version of the referent.
63+
type: string
64+
fieldPath:
65+
description: |-
66+
If referring to a piece of an object instead of an entire object, this string
67+
should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2].
68+
For example, if the object reference is to a container within a pod, this would take on a value like:
69+
"spec.containers{name}" (where "name" refers to the name of the container that triggered
70+
the event) or if no container name is specified "spec.containers[2]" (container with
71+
index 2 in this pod). This syntax is chosen only to have some well-defined way of
72+
referencing a part of an object.
73+
type: string
74+
kind:
75+
description: |-
76+
Kind of the referent.
77+
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
78+
type: string
79+
name:
80+
description: |-
81+
Name of the referent.
82+
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
83+
type: string
84+
namespace:
85+
description: |-
86+
Namespace of the referent.
87+
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/
88+
type: string
89+
resourceVersion:
90+
description: |-
91+
Specific resourceVersion to which this reference is made, if any.
92+
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency
93+
type: string
94+
uid:
95+
description: |-
96+
UID of the referent.
97+
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids
98+
type: string
99+
type: object
100+
x-kubernetes-map-type: atomic
56101
type: object
57102
status:
58103
properties:

go/client/models_gen.go

Lines changed: 8 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

go/controller/api/v1alpha1/servicecontext_types.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package v1alpha1
22

33
import (
4+
v1 "k8s.io/api/core/v1"
45
"k8s.io/apimachinery/pkg/api/meta"
56
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
67
"k8s.io/apimachinery/pkg/runtime"
@@ -14,6 +15,11 @@ type ServiceContextSpec struct {
1415

1516
// A reusable configuration context, useful for plumbing data from external tools like terraform, pulumi, etc.
1617
Configuration runtime.RawExtension `json:"configuration,omitempty"`
18+
19+
// ProjectRef references project this service context belongs to.
20+
// If not provided, it will use the default project.
21+
// +kubebuilder:validation:Optional
22+
ProjectRef *v1.ObjectReference `json:"projectRef,omitempty"`
1723
}
1824

1925
//+kubebuilder:object:root=true
@@ -57,6 +63,18 @@ func (s *ServiceContext) GetName() string {
5763
return s.Name
5864
}
5965

66+
func (p *ServiceContext) ProjectName() string {
67+
if p.Spec.ProjectRef == nil {
68+
return ""
69+
}
70+
71+
return p.Spec.ProjectRef.Name
72+
}
73+
74+
func (p *ServiceContext) HasProjectRef() bool {
75+
return p.Spec.ProjectRef != nil
76+
}
77+
6078
func (s *ServiceContext) Diff(hasher Hasher) (changed bool, sha string, err error) {
6179
currentSha, err := hasher(s.Spec)
6280
if err != nil {

go/controller/api/v1alpha1/zz_generated.deepcopy.go

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

go/controller/config/crd/bases/deployments.plural.sh_servicecontexts.yaml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,51 @@ spec:
5353
description: the name of this service, if not provided ServiceContext's
5454
own name from ServiceContext.ObjectMeta will be used.
5555
type: string
56+
projectRef:
57+
description: |-
58+
ProjectRef references project this service context belongs to.
59+
If not provided, it will use the default project.
60+
properties:
61+
apiVersion:
62+
description: API version of the referent.
63+
type: string
64+
fieldPath:
65+
description: |-
66+
If referring to a piece of an object instead of an entire object, this string
67+
should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2].
68+
For example, if the object reference is to a container within a pod, this would take on a value like:
69+
"spec.containers{name}" (where "name" refers to the name of the container that triggered
70+
the event) or if no container name is specified "spec.containers[2]" (container with
71+
index 2 in this pod). This syntax is chosen only to have some well-defined way of
72+
referencing a part of an object.
73+
type: string
74+
kind:
75+
description: |-
76+
Kind of the referent.
77+
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
78+
type: string
79+
name:
80+
description: |-
81+
Name of the referent.
82+
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
83+
type: string
84+
namespace:
85+
description: |-
86+
Namespace of the referent.
87+
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/
88+
type: string
89+
resourceVersion:
90+
description: |-
91+
Specific resourceVersion to which this reference is made, if any.
92+
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency
93+
type: string
94+
uid:
95+
description: |-
96+
UID of the referent.
97+
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids
98+
type: string
99+
type: object
100+
x-kubernetes-map-type: atomic
56101
type: object
57102
status:
58103
properties:

go/controller/internal/controller/servicecontext_controller.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,13 @@ func (r *ServiceContextReconciler) Reconcile(ctx context.Context, req ctrl.Reque
9292
utils.MarkCondition(serviceContext.SetCondition, v1alpha1.SynchronizedConditionType, v1.ConditionFalse, v1alpha1.SynchronizedConditionReasonError, err.Error())
9393
return ctrl.Result{}, err
9494
}
95-
apiServiceContext, err := r.sync(serviceContext)
95+
96+
project, result, err := GetProject(ctx, r.Client, r.Scheme, serviceContext)
97+
if result != nil || err != nil {
98+
return handleRequeue(result, err, serviceContext.SetCondition)
99+
}
100+
101+
apiServiceContext, err := r.sync(serviceContext, project)
96102
if err != nil {
97103
logger.Error(err, "unable to create or update sa")
98104
utils.MarkCondition(serviceContext.SetCondition, v1alpha1.SynchronizedConditionType, v1.ConditionFalse, v1alpha1.SynchronizedConditionReasonError, err.Error())
@@ -108,13 +114,17 @@ func (r *ServiceContextReconciler) Reconcile(ctx context.Context, req ctrl.Reque
108114
return ctrl.Result{}, nil
109115
}
110116

111-
func (r *ServiceContextReconciler) sync(sc *v1alpha1.ServiceContext) (*console.ServiceContextFragment, error) {
117+
func (r *ServiceContextReconciler) sync(sc *v1alpha1.ServiceContext, project *v1alpha1.Project) (*console.ServiceContextFragment, error) {
112118
attributes := console.ServiceContextAttributes{}
113119
attributes.Configuration = lo.ToPtr("{}")
114120
if sc.Spec.Configuration.Raw != nil {
115121
attributes.Configuration = lo.ToPtr(string(sc.Spec.Configuration.Raw))
116122
}
117123

124+
if project != nil {
125+
attributes.ProjectID = project.Status.ID
126+
}
127+
118128
return r.ConsoleClient.SaveServiceContext(sc.GetName(), attributes)
119129
}
120130

lib/console.ex

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,13 @@ defmodule Console do
6060
end
6161
end
6262

63+
def vmetrics_creds() do
64+
case {Console.conf(:vmetrics_url), Console.conf(:vmetrics_tenant)} do
65+
{url, tenant} when is_binary(url) and is_binary(tenant) -> {:ok, url, tenant}
66+
_ -> :error
67+
end
68+
end
69+
6370
def truncate(str, len) when byte_size(str) > len,
6471
do: "#{String.slice(str, 0, len - 3)}..."
6572
def truncate(str, _), do: str

lib/console/deployments/init.ex

Lines changed: 24 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ defmodule Console.Deployments.Init do
66
alias Console.Services.Users
77
alias Console.Schema.{AccessToken, Cluster, Group, User}
88
alias Kube.Utils
9-
alias Console.Deployments.{Clusters, Git, Settings, Global}
9+
alias Console.Deployments.{Clusters, Git, Settings, Services}
1010

1111
@secret_name "console-auth-token"
1212

@@ -56,10 +56,10 @@ defmodule Console.Deployments.Init do
5656
artifact_repository_id: arepo.id,
5757
deployer_repository_id: drepo.id,
5858
})
59-
|> maybe_es()
59+
|> maybe_observability()
6060
|> Settings.create()
6161
end)
62-
|> add_operation(:logstash, fn %{artifacts_repo: arepo} -> maybe_setup_logstash(arepo, bot) end)
62+
|> add_operation(:context, fn _ -> maybe_setup_context(bot) end)
6363
|> add_operation(:secret, fn _ -> ensure_secret(Console.cloud?()) end)
6464
|> execute()
6565
end
@@ -112,10 +112,11 @@ defmodule Console.Deployments.Init do
112112
end
113113
end
114114

115-
defp maybe_es(attrs) do
115+
defp maybe_observability(attrs) do
116116
with true <- Console.cloud?(),
117117
inst when is_binary(inst) <- Console.cloud_instance(),
118-
{:ok, url, pass} <- Console.es_creds() do
118+
{:ok, url, pass} <- Console.es_creds(),
119+
{:ok, vurl, vtenant} <- Console.vmetrics_creds() do
119120
es_creds = %{
120121
host: url,
121122
user: "plrl-#{inst}",
@@ -129,30 +130,32 @@ defmodule Console.Deployments.Init do
129130
driver: :elastic,
130131
elastic: es_creds
131132
})
132-
|> put_in([:ai, :vector_store], %{enabled: true, vector_store: :elastic, elastic: es_creds})
133+
|> Map.put(:prometheus_connection, %{
134+
host: "#{vurl}/select/#{vtenant}/prometheus",
135+
user: "plrl-#{inst}",
136+
password: pass
137+
})
138+
|> put_in([:ai, :vector_store], %{
139+
enabled: true,
140+
vector_store: :elastic,
141+
elastic: es_creds
142+
})
133143
else
134144
_ -> attrs
135145
end
136146
end
137147

138-
defp maybe_setup_logstash(repo, bot) do
148+
defp maybe_setup_context(bot) do
139149
with true <- Console.cloud?(),
140150
inst when is_binary(inst) <- Console.cloud_instance(),
141-
{:ok, url, pass} <- Console.es_creds() do
142-
Global.create(%{
143-
name: "logstash",
144-
template: %{
145-
name: "logstash",
146-
namespace: "elastic",
147-
configuration: [
148-
%{name: "username", value: "plrl-#{inst}"},
149-
%{name: "password", value: pass},
150-
%{name: "esUrl", value: url}
151-
],
152-
repository_id: repo.id,
153-
git: %{ref: "main", folder: "cloud/logstash"}
151+
{:ok, url, pass} <- Console.es_creds(),
152+
{:ok, vurl, vtenant} <- Console.vmetrics_creds() do
153+
Services.save_context(%{
154+
configuration: %{
155+
elastic: %{url: url, user: "plrl-#{inst}", password: pass},
156+
vmetrics: %{url: "#{vurl}/insert/#{vtenant}/prometheus/api/v1/write", user: "plrl-#{inst}", password: pass}
154157
}
155-
}, bot)
158+
}, "plrl/cloud/observability", bot)
156159
else
157160
_ -> {:ok, %{}}
158161
end

lib/console/deployments/observability.ex

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ defmodule Console.Deployments.Observability do
2222
@cache Console.conf(:cache_adapter)
2323
@ttl :timer.minutes(30)
2424

25+
@noisy_threshold 3
26+
2527
require Logger
2628

2729
@type error :: Console.error
@@ -189,6 +191,22 @@ defmodule Console.Deployments.Observability do
189191

190192
def heat_map(_, flavor), do: {:error, "cannot aggregate utilization by #{flavor} for that resource"}
191193

194+
@doc """
195+
Queries memory usage by pod for a given cluster and filters those that don't exceed a significant threshold
196+
"""
197+
@spec noisy_neighbors(Cluster.t) :: {:ok, map} | error
198+
def noisy_neighbors(%Cluster{handle: cluster}) do
199+
queries(:noisy)
200+
|> bulk_query(%{cluster: cluster})
201+
|> case do
202+
{:ok, %{cpu: %Prometheus.Data{result: cpu} = cpu_res, memory: %Prometheus.Data{result: memory} = mem_res}} ->
203+
cpu = Enum.filter(cpu, & &1.value > @noisy_threshold)
204+
memory = Enum.filter(memory, & &1.value > @noisy_threshold)
205+
{:ok, %{cpu: %{cpu_res | result: cpu}, memory: %{mem_res | result: memory}}}
206+
err -> err
207+
end
208+
end
209+
192210
@doc """
193211
Queries opinionated metrics for a set of different, relevant scopes
194212
"""

lib/console/deployments/observability/metrics.ex

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,16 @@ defmodule Console.Deployments.Observability.Metrics do
4545
memory: ~s|sum(container_memory_working_set_bytes{cluster="$cluster"$filter,image!="",container!=""$filter}) by (node)|
4646
])
4747

48+
@noisy post_process([
49+
cpu: ~s|sum(rate(container_cpu_usage_seconds_total{container!="",cluster="$cluster"}[5m])) / sum(kube_pod_container_resource_requests_cpu_cores{cluster="$cluster") by (pod)|,
50+
memory: ~s|sum(container_memory_working_set_bytes{cluster="$cluster",image!="",container!=""}) / sum(kube_pod_container_resource_requests_memory_bytes{cluster="$cluster"}) by (pod)|
51+
])
52+
4853
def queries(:cluster), do: @cluster
4954
def queries(:node), do: @node
5055
def queries(:component), do: @component
56+
def queries(:noisy), do: @noisy
57+
5158
def queries(:heat, :pod), do: @heat
5259
def queries(:heat, :namespace), do: @heat_ns
5360
def queries(:heat, :node), do: @heat_node

0 commit comments

Comments
 (0)