@starship-ci/generator
Version:
Kubernetes manifest generator for Starship deployments
764 lines (745 loc) • 29.5 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MonitoringBuilder = exports.GrafanaDeploymentGenerator = exports.GrafanaServiceGenerator = exports.GrafanaConfigMapGenerator = exports.PrometheusDeploymentGenerator = exports.PrometheusServiceGenerator = exports.PrometheusRbacGenerator = exports.PrometheusConfigMapGenerator = void 0;
const fs_1 = require("fs");
const path_1 = require("path");
const helpers = __importStar(require("../helpers"));
/**
* Prometheus generators for monitoring
* Based on the Helm template: monitoring/prometheus.yaml
*/
class PrometheusConfigMapGenerator {
config;
constructor(config) {
this.config = config;
}
generate() {
if (!this.config.monitoring?.enabled) {
return [];
}
const name = 'prometheus-config';
return [
{
apiVersion: 'v1',
kind: 'ConfigMap',
metadata: {
name,
labels: {
...helpers.getCommonLabels(this.config),
'app.kubernetes.io/component': 'monitoring',
'app.kubernetes.io/part-of': 'starship',
'app.kubernetes.io/name': name
}
},
data: {
'prometheus.yml': this.generatePrometheusConfig()
}
}
];
}
generatePrometheusConfig() {
let config = `# my global config
global:
scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
# scrape_timeout is set to the global default (10s).
scrape_configs:
# The job name is added as a label \`job=<job_name>\` to any timeseries scraped from this config.
- job_name: 'kubernetes-apiservers'
kubernetes_sd_configs:
- role: endpoints
scheme: https
tls_config:
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
relabel_configs:
- source_labels: [__meta_kubernetes_namespace, __meta_kubernetes_service_name, __meta_kubernetes_endpoint_port_name]
action: keep
regex: default;kubernetes;https
- job_name: 'kubernetes-nodes'
scheme: https
tls_config:
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
kubernetes_sd_configs:
- role: node
relabel_configs:
- action: labelmap
regex: __meta_kubernetes_node_label_(.+)
- target_label: __address__
replacement: kubernetes.default.svc:443
- source_labels: [__meta_kubernetes_node_name]
regex: (.+)
target_label: __metrics_path__
replacement: /api/v1/nodes/\${1}/proxy/metrics
- job_name: 'kubernetes-pods'
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: true
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path]
action: replace
target_label: __metrics_path__
regex: (.+)
- source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
action: replace
regex: ([^:]+)(?::\\d+)?;(\\d+)
replacement: $1:$2
target_label: __address__
- action: labelmap
regex: __meta_kubernetes_pod_label_(.+)
- source_labels: [__meta_kubernetes_namespace]
action: replace
target_label: kubernetes_namespace
- source_labels: [__meta_kubernetes_pod_name]
action: replace
target_label: kubernetes_pod_name
- job_name: 'kube-state-metrics'
static_configs:
- targets: ['kube-state-metrics.kube-system.svc.cluster.local:8080']
- job_name: kubernetes-nodes-cadvisor
scrape_interval: 10s
scrape_timeout: 10s
scheme: https # remove if you want to scrape metrics on insecure port
tls_config:
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
kubernetes_sd_configs:
- role: node
relabel_configs:
- action: labelmap
regex: __meta_kubernetes_node_label_(.+)
# Only for Kubernetes ^1.7.3.
# See: https://github.com/prometheus/prometheus/issues/2916
- target_label: __address__
replacement: kubernetes.default.svc:443
- source_labels: [__meta_kubernetes_node_name]
regex: (.+)
target_label: __metrics_path__
replacement: /api/v1/nodes/\${1}/proxy/metrics/cadvisor
metric_relabel_configs:
- action: replace
source_labels: [id]
regex: '^/machine\\.slice/machine-rkt\\\\x2d([^\\\\]+)\\\\.+/([^/]+)\\.service$'
target_label: rkt_container_name
replacement: '\${2}-\${1}'
- action: replace
source_labels: [id]
regex: '^/system\\.slice/(.+)\\.service$'
target_label: systemd_service_name
replacement: '\${1}'
- job_name: 'kubernetes-service-endpoints'
kubernetes_sd_configs:
- role: endpoints
relabel_configs:
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scrape]
action: keep
regex: true
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_scheme]
action: replace
target_label: __scheme__
regex: (https?)
- source_labels: [__meta_kubernetes_service_annotation_prometheus_io_path]
action: replace
target_label: __metrics_path__
regex: (.+)
- source_labels: [__address__, __meta_kubernetes_service_annotation_prometheus_io_port]
action: replace
target_label: __address__
regex: ([^:]+)(?::\\d+)?;(\\d+)
replacement: $1:$2
- action: labelmap
regex: __meta_kubernetes_service_label_(.+)
- source_labels: [__meta_kubernetes_namespace]
action: replace
target_label: kubernetes_namespace
- source_labels: [__meta_kubernetes_service_name]
action: replace
target_label: kubernetes_name
- job_name: 'prometheus'
# metrics_path defaults to '/metrics'
# scheme defaults to 'http'.
static_configs:
- targets: ['localhost:9090']
`;
// Add chain-specific monitoring jobs
this.config.chains?.forEach((chain) => {
if (chain.metrics) {
const chainName = helpers.getChainName(String(chain.id));
// Genesis job
config += ` - job_name: '${chain.name}-genesis'
static_configs:
- targets: ['${chainName}-genesis.$(NAMESPACE).svc.cluster.local:26660']
labels:
instance: genesis
type: genesis
network: "${chain.name}"
`;
// Validator jobs if numValidators > 1
if (chain.numValidators && chain.numValidators > 1) {
for (let i = 0; i < chain.numValidators - 1; i++) {
config += ` - job_name: '${chain.name}-validator-${i}'
static_configs:
- targets: ['${chainName}-validator-${i}.${chainName}-validator.$(NAMESPACE).svc.cluster.local:26660']
labels:
instance: "validator-${i}"
type: validator
network: "${chain.name}"
`;
}
}
}
});
return config;
}
}
exports.PrometheusConfigMapGenerator = PrometheusConfigMapGenerator;
class PrometheusRbacGenerator {
config;
constructor(config) {
this.config = config;
}
generate() {
if (!this.config.monitoring?.enabled) {
return [];
}
const name = 'prometheus';
return [
{
apiVersion: 'rbac.authorization.k8s.io/v1',
kind: 'ClusterRole',
metadata: {
name,
labels: {
...helpers.getCommonLabels(this.config),
'app.kubernetes.io/component': 'monitoring',
'app.kubernetes.io/part-of': 'starship',
'app.kubernetes.io/name': name
}
},
rules: [
{
apiGroups: [''],
resources: [
'nodes',
'nodes/proxy',
'services',
'endpoints',
'pods'
],
verbs: ['get', 'list', 'watch']
},
{
apiGroups: ['extensions'],
resources: ['ingresses'],
verbs: ['get', 'list', 'watch']
},
{
nonResourceURLs: ['/metrics'],
verbs: ['get']
}
]
},
{
apiVersion: 'rbac.authorization.k8s.io/v1',
kind: 'ClusterRoleBinding',
metadata: {
name,
labels: {
...helpers.getCommonLabels(this.config),
'app.kubernetes.io/component': 'monitoring',
'app.kubernetes.io/part-of': 'starship',
'app.kubernetes.io/name': name
}
},
roleRef: {
apiGroup: 'rbac.authorization.k8s.io',
kind: 'ClusterRole',
name: 'prometheus'
},
subjects: [
{
kind: 'ServiceAccount',
name: 'default',
namespace: '$(NAMESPACE)'
}
]
}
];
}
}
exports.PrometheusRbacGenerator = PrometheusRbacGenerator;
class PrometheusServiceGenerator {
config;
constructor(config) {
this.config = config;
}
generate() {
if (!this.config.monitoring?.enabled) {
return [];
}
return [
{
apiVersion: 'v1',
kind: 'Service',
metadata: {
name: 'prometheus',
labels: {
...helpers.getCommonLabels(this.config),
'app.kubernetes.io/component': 'monitoring',
'app.kubernetes.io/part-of': 'starship',
'app.kubernetes.io/name': 'prometheus'
},
annotations: {
'prometheus.io/scrape': 'true',
'prometheus.io/port': '9090'
}
},
spec: {
clusterIP: 'None',
ports: [
{
name: 'http',
port: 9090,
protocol: 'TCP',
targetPort: '9090'
}
],
selector: {
'app.kubernetes.io/name': 'prometheus'
}
}
}
];
}
}
exports.PrometheusServiceGenerator = PrometheusServiceGenerator;
class PrometheusDeploymentGenerator {
config;
constructor(config) {
this.config = config;
}
generate() {
if (!this.config.monitoring?.enabled) {
return [];
}
return [
{
apiVersion: 'apps/v1',
kind: 'Deployment',
metadata: {
name: 'prometheus',
labels: {
...helpers.getCommonLabels(this.config),
'app.kubernetes.io/component': 'monitoring',
'app.kubernetes.io/part-of': 'starship',
'app.kubernetes.io/name': 'prometheus'
}
},
spec: {
replicas: 1,
selector: {
matchLabels: {
'app.kubernetes.io/name': 'prometheus'
}
},
template: {
metadata: {
labels: {
'app.kubernetes.io/instance': 'monitoring',
'app.kubernetes.io/name': 'prometheus'
},
annotations: {
'prometheus.io/scrape': 'true',
'prometheus.io/port': '9090'
}
},
spec: {
containers: [
{
name: 'prometheus',
image: 'prom/prometheus',
args: [
'--storage.tsdb.retention=6h',
'--storage.tsdb.path=/prometheus',
'--config.file=/etc/prometheus/prometheus.yml'
],
ports: [
{
name: 'web',
containerPort: 9090
}
],
resources: helpers.getResourceObject(this.config.monitoring?.resources || {
cpu: '0.2',
memory: '400M'
}),
volumeMounts: [
{
name: 'prometheus-config-volume',
mountPath: '/etc/prometheus'
},
{
name: 'prometheus-storage-volume',
mountPath: '/prometheus'
}
]
}
],
restartPolicy: 'Always',
volumes: [
{
name: 'prometheus-config-volume',
configMap: {
defaultMode: 420,
name: 'prometheus-config'
}
},
{
name: 'prometheus-storage-volume',
emptyDir: {}
}
]
}
}
}
}
];
}
}
exports.PrometheusDeploymentGenerator = PrometheusDeploymentGenerator;
/**
* Grafana generators for monitoring
* Based on the Helm template: monitoring/grafana.yaml
*/
class GrafanaConfigMapGenerator {
config;
projectRoot;
constructor(config, projectRoot = process.cwd()) {
this.config = config;
this.projectRoot = projectRoot;
}
loadGrafanaDashboards() {
const dashboards = {};
const dashboardsDir = (0, path_1.join)(this.projectRoot, 'configs', 'grafana-dashboards');
try {
const files = (0, fs_1.readdirSync)(dashboardsDir);
if (!files.length) {
throw new Error(`Expected to find Grafana dashboard configuration files in '${dashboardsDir}' but directory is empty. Please ensure dashboard JSON files are present.`);
}
files.forEach((file) => {
if (file.endsWith('.json')) {
const filePath = (0, path_1.join)(dashboardsDir, file);
const content = (0, fs_1.readFileSync)(filePath, 'utf-8');
dashboards[file] = content;
}
});
if (Object.keys(dashboards).length === 0) {
throw new Error(`Expected to find Grafana dashboard JSON files in '${dashboardsDir}' but no .json files were found. Please ensure dashboard configuration files are present.`);
}
}
catch (error) {
if (error instanceof Error &&
error.message.includes('Expected to find')) {
throw error; // Re-throw our custom errors
}
throw new Error(`Failed to load Grafana dashboard configurations from '${dashboardsDir}'. Please ensure the directory exists and contains dashboard JSON files. Error: ${error}`);
}
return dashboards;
}
generate() {
if (!this.config.monitoring?.enabled) {
return [];
}
return [
{
apiVersion: 'v1',
kind: 'ConfigMap',
metadata: {
name: 'grafana-datasources',
labels: {
...helpers.getCommonLabels(this.config),
'app.kubernetes.io/component': 'monitoring',
'app.kubernetes.io/part-of': 'starship',
'app.kubernetes.io/name': 'grafana-datasources'
}
},
data: {
'prometheus.yaml': JSON.stringify({
apiVersion: 1,
datasources: [
{
access: 'proxy',
editable: true,
name: 'prometheus',
orgId: 1,
type: 'prometheus',
url: 'http://prometheus.aws-starship.svc:9090',
version: 1
}
]
}, null, 2)
}
},
{
apiVersion: 'v1',
kind: 'ConfigMap',
metadata: {
name: 'grafana-dashboard-providers',
labels: {
...helpers.getCommonLabels(this.config),
'app.kubernetes.io/component': 'monitoring',
'app.kubernetes.io/part-of': 'starship',
'app.kubernetes.io/name': 'grafana-dashboard-providers'
}
},
data: {
'default.yaml': JSON.stringify({
apiVersion: 1,
providers: [
{
name: 'chain-dashboard',
orgId: 1,
type: 'file',
allowUiUpdates: true,
options: {
path: '/var/lib/grafana/dashboards'
}
}
]
}, null, 2)
}
},
{
apiVersion: 'v1',
kind: 'ConfigMap',
metadata: {
name: 'grafana-dashboards',
labels: {
...helpers.getCommonLabels(this.config),
'app.kubernetes.io/component': 'monitoring',
'app.kubernetes.io/part-of': 'starship',
'app.kubernetes.io/name': 'grafana-dashboards'
}
},
data: this.loadGrafanaDashboards()
}
];
}
}
exports.GrafanaConfigMapGenerator = GrafanaConfigMapGenerator;
class GrafanaServiceGenerator {
config;
constructor(config) {
this.config = config;
}
generate() {
if (!this.config.monitoring?.enabled) {
return [];
}
return [
{
apiVersion: 'v1',
kind: 'Service',
metadata: {
name: 'grafana',
labels: {
...helpers.getCommonLabels(this.config),
'app.kubernetes.io/component': 'monitoring',
'app.kubernetes.io/part-of': 'starship',
'app.kubernetes.io/name': 'grafana'
},
annotations: {
'prometheus.io/scrape': 'true',
'prometheus.io/port': '8080'
}
},
spec: {
clusterIP: 'None',
ports: [
{
name: 'http',
port: 8080,
targetPort: 8080
}
],
selector: {
'app.kubernetes.io/name': 'grafana'
}
}
}
];
}
}
exports.GrafanaServiceGenerator = GrafanaServiceGenerator;
class GrafanaDeploymentGenerator {
config;
constructor(config) {
this.config = config;
}
generate() {
if (!this.config.monitoring?.enabled) {
return [];
}
return [
{
apiVersion: 'apps/v1',
kind: 'Deployment',
metadata: {
name: 'grafana',
labels: {
...helpers.getCommonLabels(this.config),
'app.kubernetes.io/component': 'monitoring',
'app.kubernetes.io/part-of': 'starship',
'app.kubernetes.io/name': 'grafana'
}
},
spec: {
replicas: 1,
selector: {
matchLabels: {
'app.kubernetes.io/name': 'grafana'
}
},
template: {
metadata: {
name: 'grafana',
labels: {
'app.kubernetes.io/instance': 'monitoring',
'app.kubernetes.io/name': 'grafana'
}
},
spec: {
containers: [
{
name: 'grafana',
image: 'grafana/grafana:latest',
env: [
{ name: 'GF_SERVER_HTTP_PORT', value: '8080' },
{ name: 'GF_SERVER_HTTP_ADDR', value: '0.0.0.0' },
{ name: 'GF_AUTH_DISABLE_LOGIN_FORM', value: 'true' },
{ name: 'GF_AUTH_ANONYMOUS_ENABLED', value: 'true' },
{ name: 'GF_AUTH_ANONYMOUS_ORG_NAME', value: 'Main Org.' },
{ name: 'GF_AUTH_ANONYMOUS_ORG_ROLE', value: 'Editor' }
],
ports: [
{
name: 'grafana',
containerPort: 3000
}
],
resources: helpers.getResourceObject(this.config.monitoring.resources || {
cpu: '0.2',
memory: '400M'
}),
volumeMounts: [
{
mountPath: '/var/lib/grafana',
name: 'grafana-storage'
},
{
mountPath: '/etc/grafana/provisioning/datasources',
name: 'grafana-datasources',
readOnly: false
},
{
mountPath: '/etc/grafana/provisioning/dashboards',
name: 'grafana-dashboard-providers',
readOnly: false
},
{
mountPath: '/var/lib/grafana/dashboards',
name: 'grafana-dashboards',
readOnly: false
}
]
}
],
volumes: [
{
name: 'grafana-datasources',
configMap: {
defaultMode: 420,
name: 'grafana-datasources'
}
},
{
name: 'grafana-dashboard-providers',
configMap: {
defaultMode: 420,
name: 'grafana-dashboard-providers'
}
},
{
name: 'grafana-dashboards',
configMap: {
name: 'grafana-dashboards'
}
},
{
name: 'grafana-storage',
emptyDir: {}
}
]
}
}
}
}
];
}
}
exports.GrafanaDeploymentGenerator = GrafanaDeploymentGenerator;
/**
* Main Monitoring builder
* Orchestrates Prometheus and Grafana generation
*/
class MonitoringBuilder {
config;
generators;
constructor(config, projectRoot = process.cwd()) {
this.config = config;
this.generators = [];
if (this.config.monitoring?.enabled) {
this.generators = [
// Prometheus
new PrometheusRbacGenerator(config),
new PrometheusConfigMapGenerator(config),
new PrometheusServiceGenerator(config),
new PrometheusDeploymentGenerator(config),
// Grafana
new GrafanaConfigMapGenerator(config, projectRoot),
new GrafanaServiceGenerator(config),
new GrafanaDeploymentGenerator(config)
];
}
}
generate() {
return this.generators.flatMap((generator) => generator.generate());
}
}
exports.MonitoringBuilder = MonitoringBuilder;