diff --git a/bin/single-new-eks-awsnative-cost-monitoring.ts b/bin/single-new-eks-awsnative-cost-monitoring.ts new file mode 100644 index 00000000..b578ac3c --- /dev/null +++ b/bin/single-new-eks-awsnative-cost-monitoring.ts @@ -0,0 +1,6 @@ +import SingleNewEksCostMonitoringPattern from '../lib/single-new-eks-cost-monitoring-pattern'; +import { configureApp } from '../lib/common/construct-utils'; + +const app = configureApp(); + +new SingleNewEksCostMonitoringPattern(app, "single-new-eks-awsnative-cost"); diff --git a/lib/single-new-eks-cost-monitoring-pattern/grafanaoperatorsecretaddon.ts b/lib/single-new-eks-cost-monitoring-pattern/grafanaoperatorsecretaddon.ts new file mode 100644 index 00000000..d1048667 --- /dev/null +++ b/lib/single-new-eks-cost-monitoring-pattern/grafanaoperatorsecretaddon.ts @@ -0,0 +1,104 @@ +import 'source-map-support/register'; +import * as blueprints from '@aws-quickstart/eks-blueprints'; +import * as eks from "aws-cdk-lib/aws-eks"; +import { ManagedPolicy } from "aws-cdk-lib/aws-iam"; +import { Construct } from 'constructs'; +import { createNamespace, dependable } from '@aws-quickstart/eks-blueprints/dist/utils'; + +export class GrafanaOperatorSecretAddon implements blueprints.ClusterAddOn { + id?: string | undefined; + @dependable(blueprints.addons.ExternalsSecretsAddOn.name, blueprints.addons.GrafanaOperatorAddon.name) + deploy(clusterInfo: blueprints.ClusterInfo): void | Promise { + const cluster = clusterInfo.cluster; + + const serviceAccount1 = cluster.addServiceAccount("kubecost-cost-analyzer-amp", { + name: "kubecost-cost-analyzer-amp", + namespace: "kubecost" + }); + + const policy1 = ManagedPolicy.fromAwsManagedPolicyName("AmazonPrometheusQueryAccess"); + serviceAccount1.role.addManagedPolicy(policy1); + + const policy2 = ManagedPolicy.fromAwsManagedPolicyName("AmazonPrometheusRemoteWriteAccess"); + serviceAccount1.role.addManagedPolicy(policy2); + + const serviceAccount2 = cluster.addServiceAccount("kubecost-prometheus-server-amp", { + name: "kubecost-prometheus-server-amp", + namespace: "kubecost" + }); + + const policy3 = ManagedPolicy.fromAwsManagedPolicyName("AmazonPrometheusQueryAccess"); + serviceAccount2.role.addManagedPolicy(policy3); + + const policy4 = ManagedPolicy.fromAwsManagedPolicyName("AmazonPrometheusRemoteWriteAccess"); + serviceAccount2.role.addManagedPolicy(policy4); + + const namespace = createNamespace("kubecost",cluster); + + serviceAccount1.node.addDependency(namespace); + serviceAccount2.node.addDependency(namespace); + + const secretStore = new eks.KubernetesManifest(clusterInfo.cluster.stack, "ClusterSecretStore", { + cluster: cluster, + manifest: [ + { + apiVersion: "external-secrets.io/v1beta1", + kind: "ClusterSecretStore", + metadata: { + name: "ssm-parameter-store", + namespace: "default" + }, + spec: { + provider: { + aws: { + service: "ParameterStore", + region: clusterInfo.cluster.stack.region, + auth: { + jwt: { + serviceAccountRef: { + name: "external-secrets-sa", + namespace: "external-secrets", + }, + }, + }, + }, + }, + }, + }, + ], + }); + + const externalSecret = new eks.KubernetesManifest(clusterInfo.cluster.stack, "ExternalSecret", { + cluster: cluster, + manifest: [ + { + apiVersion: "external-secrets.io/v1beta1", + kind: "ExternalSecret", + metadata: { + name: "external-grafana-admin-credentials", + namespace: "grafana-operator" + }, + spec: { + secretStoreRef: { + name: "ssm-parameter-store", + kind: "ClusterSecretStore", + }, + target: { + name: "grafana-admin-credentials" + }, + data: [ + { + secretKey: "GF_SECURITY_ADMIN_APIKEY", + remoteRef: { + key: "/cdk-accelerator/grafana-api-key" + }, + }, + ], + }, + }, + ], + }); + externalSecret.node.addDependency(secretStore); + return Promise.resolve(secretStore); + } +} \ No newline at end of file diff --git a/lib/single-new-eks-cost-monitoring-pattern/index.ts b/lib/single-new-eks-cost-monitoring-pattern/index.ts new file mode 100644 index 00000000..0136a2e9 --- /dev/null +++ b/lib/single-new-eks-cost-monitoring-pattern/index.ts @@ -0,0 +1,101 @@ +import { Construct } from 'constructs'; +import * as blueprints from '@aws-quickstart/eks-blueprints'; +import { ObservabilityBuilder } from '@aws-quickstart/eks-blueprints'; +import { GrafanaOperatorSecretAddon } from './grafanaoperatorsecretaddon'; +import { KubecostAddOn } from '@kubecost/kubecost-eks-blueprints-addon'; +import * as amp from 'aws-cdk-lib/aws-aps'; + +export default class SingleNewEksCostMonitoringPattern { + constructor(scope: Construct, id: string) { + + const stackId = `${id}-observability-accelerator`; + const account = process.env.COA_ACCOUNT_ID! || process.env.CDK_DEFAULT_ACCOUNT!; + const region = process.env.COA_AWS_REGION! || process.env.CDK_DEFAULT_REGION!; + + const ampWorkspaceName = process.env.COA_AMP_WORKSPACE_NAME! || 'observability-amp-workspace'; + const ampWorkspace = blueprints.getNamedResource(ampWorkspaceName) as unknown as amp.CfnWorkspace; + const ampEndpoint = ampWorkspace.attrPrometheusEndpoint; + const ampWorkspaceArn = ampWorkspace.attrArn; + + const queryUrl = `https://aps-workspaces.us-west-2.amazonaws.com/workspaces/ws-bd2b2b87-6484-4349-8753-fa46557e6031/api/v1/query`; + + const ampAddOnProps: blueprints.AmpAddOnProps = { + ampPrometheusEndpoint: ampEndpoint, + ampRules: { + ampWorkspaceArn: ampWorkspaceArn, + ruleFilePaths: [ + __dirname + '/../common/resources/amp-config/alerting-rules.yml', + __dirname + '/../common/resources/amp-config/recording-rules.yml' + ] + } + }; + + Reflect.defineMetadata("ordered", true, blueprints.addons.GrafanaOperatorAddon); + const addOns: Array = [ + new blueprints.addons.CloudWatchLogsAddon({ + logGroupPrefix: `/aws/eks/${stackId}`, + logRetentionDays: 30 + }), + new blueprints.addons.EbsCsiDriverAddOn(), + new blueprints.addons.ExternalsSecretsAddOn(), + new blueprints.SecretsStoreAddOn({ rotationPollInterval: "120s" }), + new blueprints.SSMAgentAddOn(), + new KubecostAddOn({ + namespace:"kubecost", + values: { + global: { + amp: { + enabled: true, + prometheusServerEndpoint: queryUrl, + remoteWriteService: ampEndpoint, + sigv4: { + region: region + } + } + }, + kubecostProductConfigs: { + clusterName: stackId, + projectID: account + }, + prometheus: { + server: { + global: { + external_labels: { + cluster_id: stackId + } + } + } + }, + serviceAccount:{ + name: "kubecost-cost-analyzer-amp", + create: false, + server: { + create: false, + name: "kubecost-prometheus-server-amp" + } + }, + federatedETL:{ + federator: { + useMultiClusterDB : true + } + } + } + + }), + new blueprints.addons.GrafanaOperatorAddon({ + createNamespace: true, + }), + new GrafanaOperatorSecretAddon() + ]; + + ObservabilityBuilder.builder() + .account(account) + .region(region) + .version('auto') + .withAmpProps(ampAddOnProps) + .resourceProvider(ampWorkspaceName, new blueprints.CreateAmpProvider(ampWorkspaceName, ampWorkspaceName)) + .enableNativePatternAddOns() + .addOns(...addOns) + .build(scope, stackId); + } +} diff --git a/package.json b/package.json index d39b10f5..f888409d 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ }, "dependencies": { "@aws-quickstart/eks-blueprints": "^1.12.0", + "@kubecost/kubecost-eks-blueprints-addon": "^0.1.8", "aws-cdk": "2.99.1", "aws-sdk": "^2.1455.0", "constructs": "^10.2.33", @@ -35,4 +36,4 @@ "aws-cdk": "2.99.1", "xml2js": "0.5.0" } -} \ No newline at end of file +}