raijin-server 0.3.4__py3-none-any.whl → 0.3.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of raijin-server might be problematic. Click here for more details.

@@ -1,233 +0,0 @@
1
- """Provisiona dashboards opinativos do Grafana e alertas padrao do Prometheus/Alertmanager."""
2
-
3
- from __future__ import annotations
4
-
5
- import json
6
- import textwrap
7
- from pathlib import Path
8
-
9
- import typer
10
-
11
- from raijin_server.utils import (
12
- ExecutionContext,
13
- kubectl_apply,
14
- kubectl_create_ns,
15
- require_root,
16
- write_file,
17
- )
18
-
19
- MANIFEST_PATH = Path("/tmp/raijin-observability-dashboards.yaml")
20
-
21
- CLUSTER_DASHBOARD = {
22
- "title": "Raijin Cluster Overview",
23
- "uid": "raijin-cluster",
24
- "timezone": "browser",
25
- "schemaVersion": 39,
26
- "panels": [
27
- {
28
- "type": "timeseries",
29
- "title": "Node CPU %",
30
- "datasource": "Prometheus",
31
- "targets": [
32
- {
33
- "expr": '100 - (avg by(instance) (irate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)',
34
- "legendFormat": "{{instance}}",
35
- }
36
- ],
37
- },
38
- {
39
- "type": "timeseries",
40
- "title": "Node Memory %",
41
- "datasource": "Prometheus",
42
- "targets": [
43
- {
44
- "expr": '((node_memory_MemTotal_bytes - node_memory_MemAvailable_bytes) / node_memory_MemTotal_bytes) * 100',
45
- "legendFormat": "{{instance}}",
46
- }
47
- ],
48
- },
49
- {
50
- "type": "stat",
51
- "title": "API Server Errors (5m)",
52
- "datasource": "Prometheus",
53
- "targets": [
54
- {
55
- "expr": 'sum(rate(apiserver_request_total{code=~"5.."}[5m]))',
56
- }
57
- ],
58
- },
59
- ],
60
- }
61
-
62
- SLO_DASHBOARD = {
63
- "title": "Raijin SLO - Workloads",
64
- "uid": "raijin-slo",
65
- "schemaVersion": 39,
66
- "panels": [
67
- {
68
- "type": "timeseries",
69
- "title": "Pod Restarts (1h)",
70
- "datasource": "Prometheus",
71
- "targets": [
72
- {
73
- "expr": 'increase(kube_pod_container_status_restarts_total[1h])',
74
- "legendFormat": "{{namespace}}/{{pod}}",
75
- }
76
- ],
77
- },
78
- {
79
- "type": "timeseries",
80
- "title": "Ingress HTTP 5xx Rate",
81
- "datasource": "Prometheus",
82
- "targets": [
83
- {
84
- "expr": 'sum(rate(traefik_service_requests_total{code=~"5.."}[5m])) by (service)',
85
- "legendFormat": "{{service}}",
86
- }
87
- ],
88
- },
89
- {
90
- "type": "gauge",
91
- "title": "Cluster CPU Pressure",
92
- "datasource": "Prometheus",
93
- "targets": [
94
- {
95
- "expr": 'avg(node_pressure_cpu_waiting_seconds_total)',
96
- }
97
- ],
98
- },
99
- ],
100
- }
101
-
102
-
103
- def _json_block(obj: dict) -> str:
104
- return json.dumps(obj, indent=2, separators=(",", ": "))
105
-
106
-
107
- def _alertmanager_block(contact_email: str, webhook_url: str) -> str:
108
- receivers = ["receivers:", " - name: default"]
109
- if contact_email:
110
- receivers.append(" email_configs:")
111
- receivers.append(f" - to: {contact_email}")
112
- receivers.append(" send_resolved: true")
113
- if webhook_url:
114
- receivers.append(" webhook_configs:")
115
- receivers.append(f" - url: {webhook_url}")
116
- receivers.append(" send_resolved: true")
117
- receivers_str = "\n".join(receivers) if len(receivers) > 2 else "receivers:\n - name: default"
118
- return textwrap.dedent(
119
- f"""
120
- route:
121
- receiver: default
122
- group_by: ['alertname']
123
- group_wait: 30s
124
- group_interval: 5m
125
- repeat_interval: 4h
126
- {receivers_str}
127
- """
128
- ).strip()
129
-
130
-
131
- def _build_manifest(namespace: str, contact_email: str, webhook_url: str) -> str:
132
- cluster_json = textwrap.indent(_json_block(CLUSTER_DASHBOARD), " ")
133
- slo_json = textwrap.indent(_json_block(SLO_DASHBOARD), " ")
134
- alertmanager_yaml = textwrap.indent(_alertmanager_block(contact_email, webhook_url), " ")
135
-
136
- prometheus_rules = textwrap.dedent(
137
- f"""
138
- apiVersion: monitoring.coreos.com/v1
139
- kind: PrometheusRule
140
- metadata:
141
- name: raijin-default-alerts
142
- namespace: {namespace}
143
- spec:
144
- groups:
145
- - name: raijin-cluster-health
146
- rules:
147
- - alert: NodeDown
148
- expr: kube_node_status_condition{{condition="Ready",status="true"}} == 0
149
- for: 5m
150
- labels:
151
- severity: critical
152
- annotations:
153
- summary: "Node fora do cluster"
154
- description: "O node {{ $labels.node }} nao reporta Ready ha 5 minutos."
155
- - alert: HighNodeCPU
156
- expr: avg(node_load1) by (instance) > count(node_cpu_seconds_total{{mode="system"}}) by (instance)
157
- for: 10m
158
- labels:
159
- severity: warning
160
- annotations:
161
- summary: "CPU elevada em {{ $labels.instance }}"
162
- description: "Load1 superior ao numero de cores ha 10 minutos."
163
- - alert: APIServerErrorRate
164
- expr: sum(rate(apiserver_request_total{{code=~"5.."}}[5m])) > 5
165
- for: 5m
166
- labels:
167
- severity: warning
168
- annotations:
169
- summary: "API Server retornando 5xx"
170
- description: "Taxa de erros 5xx do API Server acima de 5 req/s."
171
- """
172
- ).strip()
173
-
174
- grafana_cm = textwrap.dedent(
175
- f"""
176
- apiVersion: v1
177
- kind: ConfigMap
178
- metadata:
179
- name: grafana-dashboards-raijin
180
- namespace: {namespace}
181
- labels:
182
- grafana_dashboard: "1"
183
- data:
184
- cluster-overview.json: |
185
- {cluster_json}
186
- slo-workloads.json: |
187
- {slo_json}
188
- """
189
- ).strip()
190
-
191
- alertmanager_cm = textwrap.dedent(
192
- f"""
193
- apiVersion: v1
194
- kind: ConfigMap
195
- metadata:
196
- name: alertmanager-raijin
197
- namespace: {namespace}
198
- labels:
199
- app.kubernetes.io/name: alertmanager
200
- data:
201
- alertmanager.yml: |
202
- {alertmanager_yaml}
203
- """
204
- ).strip()
205
-
206
- documents = "\n---\n".join([grafana_cm, alertmanager_cm, prometheus_rules])
207
- return documents + "\n"
208
-
209
-
210
- def run(ctx: ExecutionContext) -> None:
211
- require_root(ctx)
212
- typer.echo("Aplicando dashboards e alertas opinativos...")
213
-
214
- namespace = typer.prompt("Namespace de observabilidade", default="observability")
215
- contact_email = typer.prompt(
216
- "Email para receber alertas (ENTER para ignorar)",
217
- default="",
218
- )
219
- webhook_url = typer.prompt(
220
- "Webhook HTTP (Slack/Teams) para Alertmanager (ENTER para ignorar)",
221
- default="",
222
- )
223
-
224
- kubectl_create_ns(namespace, ctx)
225
-
226
- manifest = _build_manifest(namespace, contact_email, webhook_url)
227
- write_file(MANIFEST_PATH, manifest, ctx)
228
- kubectl_apply(str(MANIFEST_PATH), ctx)
229
-
230
- typer.secho("Dashboards e alertas aplicados no namespace de observabilidade.", fg=typer.colors.GREEN)
231
- typer.echo("Grafana: ConfigMap 'grafana-dashboards-raijin'")
232
- typer.echo("Alertmanager: ConfigMap 'alertmanager-raijin'")
233
- typer.echo("Prometheus: PrometheusRule 'raijin-default-alerts'")
@@ -1,246 +0,0 @@
1
- """Provisiona ingressos seguros para Grafana, Prometheus e Alertmanager."""
2
-
3
- from __future__ import annotations
4
-
5
- import base64
6
- import subprocess
7
- from pathlib import Path
8
- from typing import Dict, List
9
-
10
- import typer
11
-
12
- from raijin_server.utils import (
13
- ExecutionContext,
14
- kubectl_apply,
15
- kubectl_create_ns,
16
- require_root,
17
- write_file,
18
- )
19
-
20
- MANIFEST_PATH = Path("/tmp/raijin-observability-ingress.yaml")
21
-
22
- COMPONENTS: List[Dict[str, object]] = [
23
- {
24
- "key": "grafana",
25
- "label": "Grafana",
26
- "service": "grafana",
27
- "port": 80,
28
- "default_host": "grafana.example.com",
29
- "default_tls": "grafana-tls",
30
- "auth_secret": "grafana-basic-auth",
31
- "middleware": "grafana-auth",
32
- },
33
- {
34
- "key": "prometheus",
35
- "label": "Prometheus",
36
- "service": "kube-prometheus-stack-prometheus",
37
- "port": 9090,
38
- "default_host": "prometheus.example.com",
39
- "default_tls": "prometheus-tls",
40
- "auth_secret": "prometheus-basic-auth",
41
- "middleware": "prometheus-auth",
42
- },
43
- {
44
- "key": "alertmanager",
45
- "label": "Alertmanager",
46
- "service": "kube-prometheus-stack-alertmanager",
47
- "port": 9093,
48
- "default_host": "alerts.example.com",
49
- "default_tls": "alertmanager-tls",
50
- "auth_secret": "alertmanager-basic-auth",
51
- "middleware": "alertmanager-auth",
52
- },
53
- ]
54
-
55
-
56
- def _generate_htpasswd(username: str, password: str) -> str:
57
- try:
58
- result = subprocess.run(
59
- ["openssl", "passwd", "-6", password],
60
- capture_output=True,
61
- text=True,
62
- check=True,
63
- )
64
- hashed = result.stdout.strip()
65
- except Exception:
66
- import crypt
67
-
68
- hashed = crypt.crypt(password, crypt.mksalt(crypt.METHOD_SHA512))
69
- return f"{username}:{hashed}"
70
-
71
-
72
- def _build_manifest(
73
- namespace: str,
74
- ingress_class: str,
75
- services: List[Dict[str, object]],
76
- encoded_users: str,
77
- issuer_name: str,
78
- issuer_kind: str,
79
- ) -> str:
80
- documents: List[str] = []
81
-
82
- for svc in services:
83
- host = svc["host"]
84
- tls_secret = svc["tls_secret"]
85
- auth_secret = svc["auth_secret"]
86
- middleware = svc["middleware"]
87
- service_name = svc["service"]
88
- port = svc["port"]
89
- name = svc["key"]
90
-
91
- documents.append(
92
- f"""apiVersion: v1
93
- kind: Secret
94
- metadata:
95
- name: {auth_secret}
96
- namespace: {namespace}
97
- type: Opaque
98
- data:
99
- users: {encoded_users}
100
- """
101
- )
102
-
103
- documents.append(
104
- f"""apiVersion: traefik.containo.us/v1alpha1
105
- kind: Middleware
106
- metadata:
107
- name: {middleware}
108
- namespace: {namespace}
109
- spec:
110
- basicAuth:
111
- secret: {auth_secret}
112
- removeHeader: true
113
- """
114
- )
115
-
116
- if issuer_name:
117
- documents.append(
118
- f"""apiVersion: cert-manager.io/v1
119
- kind: Certificate
120
- metadata:
121
- name: {name}-ingress-cert
122
- namespace: {namespace}
123
- spec:
124
- secretName: {tls_secret}
125
- dnsNames:
126
- - {host}
127
- issuerRef:
128
- name: {issuer_name}
129
- kind: {issuer_kind}
130
- """
131
- )
132
-
133
- documents.append(
134
- f"""apiVersion: networking.k8s.io/v1
135
- kind: Ingress
136
- metadata:
137
- name: {name}-secure
138
- namespace: {namespace}
139
- annotations:
140
- traefik.ingress.kubernetes.io/router.middlewares: {namespace}-{middleware}@kubernetescrd
141
- spec:
142
- ingressClassName: {ingress_class}
143
- rules:
144
- - host: {host}
145
- http:
146
- paths:
147
- - path: /
148
- pathType: Prefix
149
- backend:
150
- service:
151
- name: {service_name}
152
- port:
153
- number: {port}
154
- tls:
155
- - secretName: {tls_secret}
156
- hosts:
157
- - {host}
158
- """
159
- )
160
-
161
- return "---\n".join(documents)
162
-
163
-
164
- def run(ctx: ExecutionContext) -> None:
165
- require_root(ctx)
166
-
167
- typer.secho("⚠️ AVISO DE SEGURANÇA", fg=typer.colors.RED, bold=True)
168
- typer.secho(
169
- "\nExpor ferramentas de observabilidade publicamente é um RISCO DE SEGURANÇA significativo!",
170
- fg=typer.colors.YELLOW,
171
- )
172
- typer.secho(
173
- "Mesmo com TLS e BasicAuth, você estará expondo informações sensíveis do cluster.\n",
174
- fg=typer.colors.YELLOW,
175
- )
176
- typer.secho("✅ RECOMENDAÇÃO FORTE:", fg=typer.colors.GREEN, bold=True)
177
- typer.echo("1. Configure VPN: sudo raijin vpn")
178
- typer.echo("2. Use port-forward via VPN para acesso seguro")
179
- typer.echo("3. Mantenha os dashboards APENAS na rede interna\n")
180
-
181
- proceed = typer.confirm(
182
- "Você REALMENTE quer expor os dashboards publicamente?",
183
- default=False
184
- )
185
-
186
- if not proceed:
187
- typer.secho("\n✓ Boa decisão! Use VPN para acesso seguro.", fg=typer.colors.GREEN)
188
- typer.echo("\nPara acessar via VPN + port-forward:")
189
- typer.echo(" kubectl -n observability port-forward svc/grafana 3000:80")
190
- typer.echo(" kubectl -n observability port-forward svc/kube-prometheus-stack-prometheus 9090:9090")
191
- typer.echo(" kubectl -n observability port-forward svc/kube-prometheus-stack-alertmanager 9093:9093")
192
- raise typer.Exit(0)
193
-
194
- typer.echo("\nProvisionando ingress seguro para observabilidade...")
195
-
196
- namespace = typer.prompt("Namespace dos componentes", default="observability")
197
- ingress_class = typer.prompt("IngressClass dedicada", default="traefik")
198
- username = typer.prompt("Usuario para basic auth", default="observability")
199
- password = typer.prompt(
200
- "Senha para basic auth",
201
- hide_input=True,
202
- confirmation_prompt=True,
203
- )
204
-
205
- configured: List[Dict[str, object]] = []
206
- for comp in COMPONENTS:
207
- host = typer.prompt(f"Host para {comp['label']}", default=comp["default_host"])
208
- tls_secret = typer.prompt(
209
- f"Secret TLS para {comp['label']}",
210
- default=comp["default_tls"],
211
- )
212
- configured.append({**comp, "host": host, "tls_secret": tls_secret})
213
-
214
- issue_certs = typer.confirm("Gerar Certificates via cert-manager?", default=True)
215
- issuer_name = ""
216
- issuer_kind = "ClusterIssuer"
217
- if issue_certs:
218
- issuer_name = typer.prompt("Nome do issuer (cert-manager)", default="letsencrypt-prod")
219
- issuer_kind = typer.prompt("Tipo do issuer (Issuer/ClusterIssuer)", default="ClusterIssuer")
220
-
221
- secret_line = _generate_htpasswd(username, password)
222
- encoded_users = base64.b64encode(secret_line.encode()).decode()
223
-
224
- kubectl_create_ns(namespace, ctx)
225
-
226
- manifest = _build_manifest(
227
- namespace,
228
- ingress_class,
229
- configured,
230
- encoded_users,
231
- issuer_name,
232
- issuer_kind,
233
- )
234
-
235
- write_file(MANIFEST_PATH, manifest, ctx)
236
- kubectl_apply(str(MANIFEST_PATH), ctx)
237
-
238
- typer.secho("Ingress seguro aplicado com sucesso.", fg=typer.colors.GREEN)
239
- typer.echo("Hosts publicados:")
240
- for svc in configured:
241
- typer.echo(f" - {svc['label']}: https://{svc['host']}")
242
- if not issuer_name:
243
- typer.secho(
244
- "Certificados nao foram criados automaticamente. Certifique-se de que os secrets TLS existem.",
245
- fg=typer.colors.YELLOW,
246
- )