kube_cluster 0.2.1 → 0.3.0
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.
- checksums.yaml +4 -4
- data/Gemfile.lock +4 -4
- data/examples/01-basic-redis-pod/manifest.rb +1 -1
- data/examples/02-manifest-with-middleware/manifest.rb +37 -0
- data/examples/02-manifest-with-middleware/middleware/labels.rb +4 -0
- data/examples/02-manifest-with-middleware/middleware/namespace.rb +4 -0
- data/examples/02-manifest-with-middleware/templates/config_map.rb +13 -0
- data/examples/02-manifest-with-middleware/templates/deployment.rb +59 -0
- data/examples/02-manifest-with-middleware/templates/horizontal_pod_autoscaler.rb +30 -0
- data/examples/02-manifest-with-middleware/templates/ingress.rb +38 -0
- data/examples/02-manifest-with-middleware/templates/service.rb +12 -0
- data/examples/{version2 → 03-app-with-database}/demo.rb +2 -2
- data/examples/{version2 → 03-app-with-database}/postgresql.rb +4 -4
- data/examples/{version2 → 03-app-with-database}/ruby_on_rails.rb +1 -1
- data/lib/kube/cluster/manifest.rb +13 -64
- data/lib/kube/cluster/middleware/annotations.rb +32 -0
- data/lib/kube/cluster/middleware/hpa_for_deployment.rb +111 -0
- data/lib/kube/cluster/{manifest/middleware → middleware}/ingress_for_service.rb +36 -34
- data/lib/kube/cluster/middleware/labels.rb +59 -0
- data/lib/kube/cluster/middleware/namespace.rb +31 -0
- data/lib/kube/cluster/middleware/pod_anti_affinity.rb +61 -0
- data/lib/kube/cluster/middleware/resource_preset.rb +64 -0
- data/lib/kube/cluster/middleware/security_context.rb +84 -0
- data/lib/kube/cluster/middleware/service_for_deployment.rb +71 -0
- data/lib/kube/cluster/middleware/stack.rb +43 -0
- data/lib/kube/cluster/middleware.rb +69 -0
- data/lib/kube/cluster/resource.rb +78 -0
- data/lib/kube/cluster/version.rb +1 -1
- data/lib/kube/cluster.rb +21 -0
- metadata +25 -19
- data/examples/database/manifest.rb +0 -238
- data/examples/web-app/manifest.rb +0 -215
- data/lib/kube/cluster/manifest/middleware/annotations.rb +0 -32
- data/lib/kube/cluster/manifest/middleware/hpa_for_deployment.rb +0 -109
- data/lib/kube/cluster/manifest/middleware/labels.rb +0 -59
- data/lib/kube/cluster/manifest/middleware/namespace.rb +0 -31
- data/lib/kube/cluster/manifest/middleware/pod_anti_affinity.rb +0 -61
- data/lib/kube/cluster/manifest/middleware/resource_preset.rb +0 -64
- data/lib/kube/cluster/manifest/middleware/security_context.rb +0 -84
- data/lib/kube/cluster/manifest/middleware/service_for_deployment.rb +0 -69
- data/lib/kube/cluster/manifest/middleware.rb +0 -178
- data/lib/kube/cluster/manifest/stack.rb +0 -56
- /data/examples/{version2 → 03-app-with-database}/helpers.rb +0 -0
- /data/examples/{version2 → 03-app-with-database}/my_app.rb +0 -0
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
# Database (PostgreSQL) Example
|
|
5
|
-
#
|
|
6
|
-
# Demonstrates the Ruby equivalents of Bitnami common chart patterns:
|
|
7
|
-
# - Secret lifecycle management (_secrets.tpl)
|
|
8
|
-
# - StorageClass resolution (_storage.tpl)
|
|
9
|
-
# - StatefulSet with persistent volumes
|
|
10
|
-
# - Headless service for stable DNS
|
|
11
|
-
# - NetworkPolicy for database isolation
|
|
12
|
-
# - Standard labels and naming (_labels.tpl, _names.tpl)
|
|
13
|
-
# - Resource presets (_resources.tpl)
|
|
14
|
-
#
|
|
15
|
-
# Usage:
|
|
16
|
-
# ruby examples/database/manifest.rb
|
|
17
|
-
# ruby examples/database/manifest.rb > database.yaml
|
|
18
|
-
|
|
19
|
-
require "kube/schema"
|
|
20
|
-
require "securerandom"
|
|
21
|
-
|
|
22
|
-
# ── Naming ────────────────────────────────────────────────────────────────────
|
|
23
|
-
|
|
24
|
-
APP_NAME = "postgresql"
|
|
25
|
-
RELEASE_NAME = "my-release"
|
|
26
|
-
NAMESPACE = "database"
|
|
27
|
-
FULLNAME = "#{RELEASE_NAME}-#{APP_NAME}"[0, 63].chomp("-")
|
|
28
|
-
|
|
29
|
-
# ── Labels ────────────────────────────────────────────────────────────────────
|
|
30
|
-
|
|
31
|
-
STANDARD_LABELS = {
|
|
32
|
-
"app.kubernetes.io/name": APP_NAME,
|
|
33
|
-
"app.kubernetes.io/instance": RELEASE_NAME,
|
|
34
|
-
"app.kubernetes.io/version": "16.4.0",
|
|
35
|
-
"app.kubernetes.io/component": "primary",
|
|
36
|
-
"app.kubernetes.io/managed-by": "kube_cluster",
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
MATCH_LABELS = STANDARD_LABELS.slice(
|
|
40
|
-
:"app.kubernetes.io/name",
|
|
41
|
-
:"app.kubernetes.io/instance",
|
|
42
|
-
:"app.kubernetes.io/component",
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
# ── Secrets (from _secrets.tpl) ───────────────────────────────────────────────
|
|
46
|
-
# Bitnami's secrets.passwords.manage generates random passwords, reuses existing
|
|
47
|
-
# ones on upgrade, and base64 encodes them. In Ruby we do this directly.
|
|
48
|
-
|
|
49
|
-
POSTGRES_PASSWORD = SecureRandom.alphanumeric(24)
|
|
50
|
-
REPLICATION_PASSWORD = SecureRandom.alphanumeric(24)
|
|
51
|
-
|
|
52
|
-
def base64(str)
|
|
53
|
-
[str].pack("m0")
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
# ── Storage (from _storage.tpl) ───────────────────────────────────────────────
|
|
57
|
-
# Bitnami resolves storage class from global > persistence > default.
|
|
58
|
-
# The "-" convention means explicitly use the default storage class (empty string).
|
|
59
|
-
|
|
60
|
-
STORAGE_CLASS = "standard" # set to "-" for default, or nil to omit
|
|
61
|
-
STORAGE_SIZE = "10Gi"
|
|
62
|
-
|
|
63
|
-
# ── Resource presets ──────────────────────────────────────────────────────────
|
|
64
|
-
|
|
65
|
-
RESOURCES = {
|
|
66
|
-
requests: { cpu: "500m", memory: "512Mi" },
|
|
67
|
-
limits: { cpu: "750m", memory: "768Mi" },
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
# ── Build manifests ───────────────────────────────────────────────────────────
|
|
71
|
-
|
|
72
|
-
manifest = Kube::Schema::Manifest.new
|
|
73
|
-
|
|
74
|
-
# -- Namespace --
|
|
75
|
-
|
|
76
|
-
manifest << Kube::Schema["Namespace"].new {
|
|
77
|
-
metadata.name = NAMESPACE
|
|
78
|
-
metadata.labels = STANDARD_LABELS.reject { |k, _| k == :"app.kubernetes.io/component" }
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
# -- Secret --
|
|
82
|
-
# Pattern from _secrets.tpl: base64-encoded credentials, separate keys for
|
|
83
|
-
# each password, supports existing secret reuse on upgrade.
|
|
84
|
-
|
|
85
|
-
manifest << Kube::Schema["Secret"].new {
|
|
86
|
-
metadata.name = FULLNAME
|
|
87
|
-
metadata.namespace = NAMESPACE
|
|
88
|
-
metadata.labels = STANDARD_LABELS
|
|
89
|
-
self.type = "Opaque"
|
|
90
|
-
self.data = {
|
|
91
|
-
"postgres-password": base64(POSTGRES_PASSWORD),
|
|
92
|
-
"replication-password": base64(REPLICATION_PASSWORD),
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
# -- Headless Service (for StatefulSet stable DNS) --
|
|
97
|
-
|
|
98
|
-
manifest << Kube::Schema["Service"].new {
|
|
99
|
-
metadata.name = "#{FULLNAME}-headless"
|
|
100
|
-
metadata.namespace = NAMESPACE
|
|
101
|
-
metadata.labels = STANDARD_LABELS
|
|
102
|
-
|
|
103
|
-
spec.clusterIP = "None"
|
|
104
|
-
spec.selector = MATCH_LABELS
|
|
105
|
-
spec.ports = [
|
|
106
|
-
{ name: "tcp-postgresql", port: 5432, targetPort: "tcp-postgresql" },
|
|
107
|
-
]
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
# -- Primary Service (for client connections) --
|
|
111
|
-
|
|
112
|
-
manifest << Kube::Schema["Service"].new {
|
|
113
|
-
metadata.name = FULLNAME
|
|
114
|
-
metadata.namespace = NAMESPACE
|
|
115
|
-
metadata.labels = STANDARD_LABELS
|
|
116
|
-
|
|
117
|
-
spec.selector = MATCH_LABELS
|
|
118
|
-
spec.ports = [
|
|
119
|
-
{ name: "tcp-postgresql", port: 5432, targetPort: "tcp-postgresql" },
|
|
120
|
-
]
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
# -- StatefulSet --
|
|
124
|
-
# Uses storage class resolution pattern, secret references, resource presets,
|
|
125
|
-
# pod anti-affinity for spreading replicas.
|
|
126
|
-
|
|
127
|
-
manifest << Kube::Schema["StatefulSet"].new {
|
|
128
|
-
metadata.name = FULLNAME
|
|
129
|
-
metadata.namespace = NAMESPACE
|
|
130
|
-
metadata.labels = STANDARD_LABELS
|
|
131
|
-
|
|
132
|
-
spec.serviceName = "#{FULLNAME}-headless"
|
|
133
|
-
spec.replicas = 1
|
|
134
|
-
spec.selector.matchLabels = MATCH_LABELS
|
|
135
|
-
|
|
136
|
-
spec.template.metadata.labels = STANDARD_LABELS
|
|
137
|
-
spec.template.spec.containers = [
|
|
138
|
-
{
|
|
139
|
-
name: APP_NAME,
|
|
140
|
-
image: "docker.io/postgres:16.4-alpine",
|
|
141
|
-
ports: [
|
|
142
|
-
{ name: "tcp-postgresql", containerPort: 5432 },
|
|
143
|
-
],
|
|
144
|
-
resources: RESOURCES,
|
|
145
|
-
env: [
|
|
146
|
-
{ name: "POSTGRES_PASSWORD", valueFrom: { secretKeyRef: { name: FULLNAME, key: "postgres-password" } } },
|
|
147
|
-
{ name: "PGDATA", value: "/var/lib/postgresql/data/pgdata" },
|
|
148
|
-
],
|
|
149
|
-
volumeMounts: [
|
|
150
|
-
{ name: "data", mountPath: "/var/lib/postgresql/data" },
|
|
151
|
-
],
|
|
152
|
-
livenessProbe: {
|
|
153
|
-
exec: { command: ["pg_isready", "-U", "postgres"] },
|
|
154
|
-
initialDelaySeconds: 30,
|
|
155
|
-
periodSeconds: 10,
|
|
156
|
-
timeoutSeconds: 5,
|
|
157
|
-
failureThreshold: 6,
|
|
158
|
-
},
|
|
159
|
-
readinessProbe: {
|
|
160
|
-
exec: { command: ["pg_isready", "-U", "postgres"] },
|
|
161
|
-
initialDelaySeconds: 5,
|
|
162
|
-
periodSeconds: 10,
|
|
163
|
-
timeoutSeconds: 5,
|
|
164
|
-
failureThreshold: 6,
|
|
165
|
-
},
|
|
166
|
-
},
|
|
167
|
-
]
|
|
168
|
-
|
|
169
|
-
# Pod anti-affinity: hard anti-affinity to guarantee one pod per node
|
|
170
|
-
# (from _affinities.tpl: common.affinities.pods.hard)
|
|
171
|
-
spec.template.spec.affinity = {
|
|
172
|
-
podAntiAffinity: {
|
|
173
|
-
requiredDuringSchedulingIgnoredDuringExecution: [
|
|
174
|
-
{
|
|
175
|
-
labelSelector: { matchLabels: MATCH_LABELS },
|
|
176
|
-
topologyKey: "kubernetes.io/hostname",
|
|
177
|
-
},
|
|
178
|
-
],
|
|
179
|
-
},
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
# Storage class resolution (from _storage.tpl)
|
|
183
|
-
storage_class = STORAGE_CLASS == "-" ? "" : STORAGE_CLASS
|
|
184
|
-
|
|
185
|
-
spec.volumeClaimTemplates = [
|
|
186
|
-
{
|
|
187
|
-
metadata: { name: "data" },
|
|
188
|
-
spec: {
|
|
189
|
-
accessModes: ["ReadWriteOnce"],
|
|
190
|
-
storageClassName: storage_class,
|
|
191
|
-
resources: { requests: { storage: STORAGE_SIZE } },
|
|
192
|
-
},
|
|
193
|
-
},
|
|
194
|
-
]
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
# -- NetworkPolicy --
|
|
198
|
-
# Isolate the database: only allow ingress from pods with the app label,
|
|
199
|
-
# deny everything else. This is a common production hardening pattern.
|
|
200
|
-
|
|
201
|
-
manifest << Kube::Schema["NetworkPolicy"].new {
|
|
202
|
-
metadata.name = FULLNAME
|
|
203
|
-
metadata.namespace = NAMESPACE
|
|
204
|
-
metadata.labels = STANDARD_LABELS
|
|
205
|
-
|
|
206
|
-
spec.podSelector = { matchLabels: MATCH_LABELS }
|
|
207
|
-
spec.policyTypes = ["Ingress", "Egress"]
|
|
208
|
-
spec.ingress = [
|
|
209
|
-
{
|
|
210
|
-
from: [
|
|
211
|
-
{
|
|
212
|
-
podSelector: {
|
|
213
|
-
matchLabels: { "app.kubernetes.io/name": "web-app" },
|
|
214
|
-
},
|
|
215
|
-
},
|
|
216
|
-
],
|
|
217
|
-
ports: [
|
|
218
|
-
{ protocol: "TCP", port: "5432" },
|
|
219
|
-
],
|
|
220
|
-
},
|
|
221
|
-
]
|
|
222
|
-
# Allow DNS egress + nothing else
|
|
223
|
-
spec.egress = [
|
|
224
|
-
{
|
|
225
|
-
to: [
|
|
226
|
-
{ namespaceSelector: {} },
|
|
227
|
-
],
|
|
228
|
-
ports: [
|
|
229
|
-
{ protocol: "UDP", port: "53" },
|
|
230
|
-
{ protocol: "TCP", port: "53" },
|
|
231
|
-
],
|
|
232
|
-
},
|
|
233
|
-
]
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
# ── Render ────────────────────────────────────────────────────────────────────
|
|
237
|
-
|
|
238
|
-
puts manifest.to_yaml
|
|
@@ -1,215 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
require "kube/schema"
|
|
5
|
-
|
|
6
|
-
MATCH_LABELS = STANDARD_LABELS.slice(
|
|
7
|
-
:"app.kubernetes.io/name",
|
|
8
|
-
:"app.kubernetes.io/instance",
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
REGISTRY = "docker.io"
|
|
12
|
-
REPOSITORY = "nginx"
|
|
13
|
-
TAG = "1.27.3-alpine"
|
|
14
|
-
IMAGE = "#{REGISTRY}/#{REPOSITORY}:#{TAG}"
|
|
15
|
-
|
|
16
|
-
RESOURCE_PRESETS = {
|
|
17
|
-
"nano": { requests: { cpu: "100m", memory: "128Mi" }, limits: { cpu: "150m", memory: "192Mi" } },
|
|
18
|
-
"micro": { requests: { cpu: "250m", memory: "256Mi" }, limits: { cpu: "375m", memory: "384Mi" } },
|
|
19
|
-
"small": { requests: { cpu: "500m", memory: "512Mi" }, limits: { cpu: "750m", memory: "768Mi" } },
|
|
20
|
-
"medium": { requests: { cpu: "500m", memory: "1024Mi" }, limits: { cpu: "750m", memory: "1536Mi" } },
|
|
21
|
-
"large": { requests: { cpu: "1.0", memory: "2048Mi" }, limits: { cpu: "1.5", memory: "3072Mi" } },
|
|
22
|
-
"xlarge": { requests: { cpu: "1.0", memory: "3072Mi" }, limits: { cpu: "3.0", memory: "6144Mi" } },
|
|
23
|
-
"2xlarge": { requests: { cpu: "1.0", memory: "3072Mi" }, limits: { cpu: "6.0", memory: "12288Mi" } },
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
module AutoConfig
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
class MyApp < Kube::Schema::Manifest
|
|
30
|
-
include Helpers
|
|
31
|
-
include AutoConfig
|
|
32
|
-
|
|
33
|
-
def initialize(namespace: DEFAULT_NAMESPACE, labels: STANDARD_LABELS)
|
|
34
|
-
@app_name = "web-app"
|
|
35
|
-
@fullname = "#{RELEASE_NAME}-#{APP_NAME}"[0, 63].chomp("-")
|
|
36
|
-
@namespace = namespace
|
|
37
|
-
|
|
38
|
-
@labels = {
|
|
39
|
-
"app.kubernetes.io/name": @app_name
|
|
40
|
-
"app.kubernetes.io/managed-by": "kube_cluster",
|
|
41
|
-
}
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
stack do
|
|
45
|
-
use Middleware::Namespace
|
|
46
|
-
use Middleware::Labels
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
class Namespace < Kube::Schema["Namespace"]
|
|
50
|
-
def initialize(namespace:)
|
|
51
|
-
build {
|
|
52
|
-
metadata.name = namespace
|
|
53
|
-
}
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
class ConfigMap < Kube::Schema["ConfigMap"]
|
|
58
|
-
def initialize(namespace:)
|
|
59
|
-
build {
|
|
60
|
-
metadata.name = "#{namespace}-config"
|
|
61
|
-
spec.data = {
|
|
62
|
-
RAILS_ENV: "production",
|
|
63
|
-
LOG_LEVEL: "info",
|
|
64
|
-
WORKERS: "4",
|
|
65
|
-
PORT: "3000",
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
end
|
|
69
|
-
end
|
|
70
|
-
|
|
71
|
-
class Deployment < Kube::Schema["Deployment"]
|
|
72
|
-
def initialize(namespace:)
|
|
73
|
-
build {
|
|
74
|
-
metadata.name = namespace
|
|
75
|
-
|
|
76
|
-
spec.replicas = 3
|
|
77
|
-
spec.selector.matchLabels = MATCH_LABELS
|
|
78
|
-
|
|
79
|
-
spec.template.metadata.labels = STANDARD_LABELS
|
|
80
|
-
spec.template.metadata.annotations = {
|
|
81
|
-
# Checksum pattern from _utils.tpl -- triggers rolling restart on config change
|
|
82
|
-
"checksum/config": "{{ sha256sum of configmap data }}",
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
spec.template.spec.containers = [
|
|
86
|
-
{
|
|
87
|
-
name: APP_NAME,
|
|
88
|
-
image: IMAGE,
|
|
89
|
-
ports: [{ name: "http", containerPort: 3000, protocol: "TCP" }],
|
|
90
|
-
resources: RESOURCES,
|
|
91
|
-
env: [
|
|
92
|
-
{ name: "PORT", value: "3000" },
|
|
93
|
-
],
|
|
94
|
-
envFrom: [
|
|
95
|
-
{ configMapRef: { name: "#{FULLNAME}-config" } },
|
|
96
|
-
],
|
|
97
|
-
livenessProbe: {
|
|
98
|
-
httpGet: { path: "/healthz", port: "http" },
|
|
99
|
-
initialDelaySeconds: 15,
|
|
100
|
-
periodSeconds: 10,
|
|
101
|
-
},
|
|
102
|
-
readinessProbe: {
|
|
103
|
-
httpGet: { path: "/readyz", port: "http" },
|
|
104
|
-
initialDelaySeconds: 5,
|
|
105
|
-
periodSeconds: 5,
|
|
106
|
-
},
|
|
107
|
-
},
|
|
108
|
-
]
|
|
109
|
-
|
|
110
|
-
# Pod anti-affinity (from _affinities.tpl)
|
|
111
|
-
# Soft anti-affinity: prefer spreading pods across nodes but don't enforce it
|
|
112
|
-
spec.template.spec.affinity = {
|
|
113
|
-
podAntiAffinity: {
|
|
114
|
-
preferredDuringSchedulingIgnoredDuringExecution: [
|
|
115
|
-
{
|
|
116
|
-
weight: 1,
|
|
117
|
-
podAffinityTerm: {
|
|
118
|
-
labelSelector: {
|
|
119
|
-
matchLabels: MATCH_LABELS,
|
|
120
|
-
},
|
|
121
|
-
topologyKey: "kubernetes.io/hostname",
|
|
122
|
-
},
|
|
123
|
-
},
|
|
124
|
-
],
|
|
125
|
-
},
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
end
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
class Service < Kube::Schema["Service"]
|
|
132
|
-
def initialize(namespace:)
|
|
133
|
-
build {
|
|
134
|
-
metadata.name = namespace
|
|
135
|
-
|
|
136
|
-
spec.selector = MATCH_LABELS
|
|
137
|
-
spec.ports = [
|
|
138
|
-
{ name: "http", port: 80, targetPort: "http", protocol: "TCP" },
|
|
139
|
-
]
|
|
140
|
-
}
|
|
141
|
-
end
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
class Ingress < Kube::Schema["Ingress"]
|
|
145
|
-
def initialize(namespace:)
|
|
146
|
-
build {
|
|
147
|
-
metadata.name = namespace
|
|
148
|
-
metadata.annotations = {
|
|
149
|
-
"cert-manager.io/cluster-issuer": "letsencrypt-prod",
|
|
150
|
-
"nginx.ingress.kubernetes.io/ssl-redirect": "true",
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
spec.ingressClassName = "nginx"
|
|
154
|
-
spec.tls = [
|
|
155
|
-
{
|
|
156
|
-
hosts: ["app.example.com"],
|
|
157
|
-
secretName: "#{namespace}-tls",
|
|
158
|
-
},
|
|
159
|
-
]
|
|
160
|
-
spec.rules = [
|
|
161
|
-
{
|
|
162
|
-
host: "app.example.com",
|
|
163
|
-
http: {
|
|
164
|
-
paths: [
|
|
165
|
-
{
|
|
166
|
-
path: "/",
|
|
167
|
-
pathType: "Prefix",
|
|
168
|
-
backend: {
|
|
169
|
-
service: {
|
|
170
|
-
name: FULLNAME,
|
|
171
|
-
port: { name: "http" },
|
|
172
|
-
},
|
|
173
|
-
},
|
|
174
|
-
},
|
|
175
|
-
],
|
|
176
|
-
},
|
|
177
|
-
},
|
|
178
|
-
]
|
|
179
|
-
}
|
|
180
|
-
end
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
class HorizontalPodAutoscaler < Kube::Schema["HorizontalPodAutoscaler"]
|
|
184
|
-
def initialize(namespace:)
|
|
185
|
-
build {
|
|
186
|
-
metadata.name = namespace
|
|
187
|
-
|
|
188
|
-
spec.scaleTargetRef.apiVersion = "apps/v1"
|
|
189
|
-
spec.scaleTargetRef.kind = "Deployment"
|
|
190
|
-
spec.scaleTargetRef.name = namespace
|
|
191
|
-
|
|
192
|
-
spec.minReplicas = 3
|
|
193
|
-
spec.maxReplicas = 10
|
|
194
|
-
spec.metrics = [
|
|
195
|
-
{
|
|
196
|
-
type: "Resource",
|
|
197
|
-
resource: {
|
|
198
|
-
name: "cpu",
|
|
199
|
-
target: { type: "Utilization", averageUtilization: 75 },
|
|
200
|
-
},
|
|
201
|
-
},
|
|
202
|
-
{
|
|
203
|
-
type: "Resource",
|
|
204
|
-
resource: {
|
|
205
|
-
name: "memory",
|
|
206
|
-
target: { type: "Utilization", averageUtilization: 80 },
|
|
207
|
-
},
|
|
208
|
-
},
|
|
209
|
-
]
|
|
210
|
-
}
|
|
211
|
-
end
|
|
212
|
-
end
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
puts MyApp.new.to_yaml
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Kube
|
|
4
|
-
module Cluster
|
|
5
|
-
class Manifest < Kube::Schema::Manifest
|
|
6
|
-
class Middleware
|
|
7
|
-
# Merges annotations into +metadata.annotations+ on every resource.
|
|
8
|
-
# Existing annotations are preserved; the supplied annotations act
|
|
9
|
-
# as defaults that can be overridden per-resource.
|
|
10
|
-
#
|
|
11
|
-
# stack do
|
|
12
|
-
# use Middleware::Annotations,
|
|
13
|
-
# "prometheus.io/scrape": "true",
|
|
14
|
-
# "prometheus.io/port": "9090"
|
|
15
|
-
# end
|
|
16
|
-
#
|
|
17
|
-
class Annotations < Middleware
|
|
18
|
-
def initialize(**annotations)
|
|
19
|
-
@annotations = annotations.transform_keys(&:to_sym).transform_values(&:to_s)
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def call(resource)
|
|
23
|
-
h = resource.to_h
|
|
24
|
-
h[:metadata] ||= {}
|
|
25
|
-
h[:metadata][:annotations] = @annotations.merge(h[:metadata][:annotations] || {})
|
|
26
|
-
rebuild(resource, h)
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
end
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Kube
|
|
4
|
-
module Cluster
|
|
5
|
-
class Manifest < Kube::Schema::Manifest
|
|
6
|
-
class Middleware
|
|
7
|
-
# Generates a HorizontalPodAutoscaler for every pod-bearing
|
|
8
|
-
# resource that carries the +app.kubernetes.io/autoscale+ label.
|
|
9
|
-
#
|
|
10
|
-
# The label value encodes the min and max replicas as "min-max":
|
|
11
|
-
#
|
|
12
|
-
# metadata.labels = { "app.kubernetes.io/autoscale": "1-5" }
|
|
13
|
-
#
|
|
14
|
-
# Options:
|
|
15
|
-
# cpu: — target CPU utilization percentage (default: 75)
|
|
16
|
-
# memory: — target memory utilization percentage (default: 80)
|
|
17
|
-
#
|
|
18
|
-
# stack do
|
|
19
|
-
# use Middleware::HPAForDeployment
|
|
20
|
-
# use Middleware::HPAForDeployment, cpu: 60, memory: 70
|
|
21
|
-
# end
|
|
22
|
-
#
|
|
23
|
-
class HPAForDeployment < Middleware
|
|
24
|
-
LABEL = :"app.kubernetes.io/autoscale"
|
|
25
|
-
|
|
26
|
-
def initialize(cpu: 75, memory: 80)
|
|
27
|
-
@cpu = cpu
|
|
28
|
-
@memory = memory
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def call(resource)
|
|
32
|
-
return resource unless pod_bearing?(resource)
|
|
33
|
-
|
|
34
|
-
value = label(resource, LABEL)
|
|
35
|
-
return resource unless value
|
|
36
|
-
|
|
37
|
-
min, max = parse_range(value)
|
|
38
|
-
|
|
39
|
-
h = resource.to_h
|
|
40
|
-
name = h.dig(:metadata, :name)
|
|
41
|
-
namespace = h.dig(:metadata, :namespace)
|
|
42
|
-
labels = h.dig(:metadata, :labels) || {}
|
|
43
|
-
api_version = h[:apiVersion] || "apps/v1"
|
|
44
|
-
resource_kind = kind(resource)
|
|
45
|
-
|
|
46
|
-
# Capture ivars as locals — the block runs via instance_exec
|
|
47
|
-
# on a BlackHoleStruct, so @ivars would resolve on the BHS.
|
|
48
|
-
cpu_target = @cpu
|
|
49
|
-
memory_target = @memory
|
|
50
|
-
|
|
51
|
-
hpa = Kube::Schema["HorizontalPodAutoscaler"].new {
|
|
52
|
-
metadata.name = name
|
|
53
|
-
metadata.namespace = namespace if namespace
|
|
54
|
-
metadata.labels = labels.reject { |k, _| k == LABEL }
|
|
55
|
-
|
|
56
|
-
spec.scaleTargetRef = {
|
|
57
|
-
apiVersion: api_version,
|
|
58
|
-
kind: resource_kind,
|
|
59
|
-
name: name,
|
|
60
|
-
}
|
|
61
|
-
spec.minReplicas = min
|
|
62
|
-
spec.maxReplicas = max
|
|
63
|
-
spec.metrics = [
|
|
64
|
-
{
|
|
65
|
-
type: "Resource",
|
|
66
|
-
resource: {
|
|
67
|
-
name: "cpu",
|
|
68
|
-
target: { type: "Utilization", averageUtilization: cpu_target },
|
|
69
|
-
},
|
|
70
|
-
},
|
|
71
|
-
{
|
|
72
|
-
type: "Resource",
|
|
73
|
-
resource: {
|
|
74
|
-
name: "memory",
|
|
75
|
-
target: { type: "Utilization", averageUtilization: memory_target },
|
|
76
|
-
},
|
|
77
|
-
},
|
|
78
|
-
]
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
[resource, hpa]
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
private
|
|
85
|
-
|
|
86
|
-
def parse_range(value)
|
|
87
|
-
parts = value.to_s.split("-", 2)
|
|
88
|
-
|
|
89
|
-
unless parts.length == 2
|
|
90
|
-
raise ArgumentError,
|
|
91
|
-
"Invalid autoscale label: #{value.inspect}. Expected format: \"min-max\" (e.g. \"1-5\")"
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
min = Integer(parts[0])
|
|
95
|
-
max = Integer(parts[1])
|
|
96
|
-
|
|
97
|
-
unless min > 0 && max >= min
|
|
98
|
-
raise ArgumentError,
|
|
99
|
-
"Invalid autoscale range: min=#{min}, max=#{max}. " \
|
|
100
|
-
"min must be > 0 and max must be >= min."
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
[min, max]
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
end
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Kube
|
|
4
|
-
module Cluster
|
|
5
|
-
class Manifest < Kube::Schema::Manifest
|
|
6
|
-
class Middleware
|
|
7
|
-
# Merges labels into +metadata.labels+ on every resource.
|
|
8
|
-
# Existing labels are preserved; the supplied labels act as defaults
|
|
9
|
-
# that can be overridden per-resource.
|
|
10
|
-
#
|
|
11
|
-
# stack do
|
|
12
|
-
# use Middleware::Labels, app: "web-app", managed_by: "kube_cluster"
|
|
13
|
-
# end
|
|
14
|
-
#
|
|
15
|
-
# The keyword arguments are converted to standard label keys:
|
|
16
|
-
#
|
|
17
|
-
# app: -> "app.kubernetes.io/name"
|
|
18
|
-
# instance: -> "app.kubernetes.io/instance"
|
|
19
|
-
# version: -> "app.kubernetes.io/version"
|
|
20
|
-
# component: -> "app.kubernetes.io/component"
|
|
21
|
-
# part_of: -> "app.kubernetes.io/part-of"
|
|
22
|
-
# managed_by: -> "app.kubernetes.io/managed-by"
|
|
23
|
-
#
|
|
24
|
-
# Any unrecognized keys are passed through as-is (string or symbol).
|
|
25
|
-
#
|
|
26
|
-
class Labels < Middleware
|
|
27
|
-
STANDARD_KEYS = {
|
|
28
|
-
app: :"app.kubernetes.io/name",
|
|
29
|
-
instance: :"app.kubernetes.io/instance",
|
|
30
|
-
version: :"app.kubernetes.io/version",
|
|
31
|
-
component: :"app.kubernetes.io/component",
|
|
32
|
-
part_of: :"app.kubernetes.io/part-of",
|
|
33
|
-
managed_by: :"app.kubernetes.io/managed-by",
|
|
34
|
-
}.freeze
|
|
35
|
-
|
|
36
|
-
def initialize(**labels)
|
|
37
|
-
@labels = normalize(labels)
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def call(resource)
|
|
41
|
-
h = resource.to_h
|
|
42
|
-
h[:metadata] ||= {}
|
|
43
|
-
h[:metadata][:labels] = @labels.merge(h[:metadata][:labels] || {})
|
|
44
|
-
rebuild(resource, h)
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
private
|
|
48
|
-
|
|
49
|
-
def normalize(labels)
|
|
50
|
-
labels.each_with_object({}) do |(key, value), result|
|
|
51
|
-
normalized_key = STANDARD_KEYS.fetch(key, key)
|
|
52
|
-
result[normalized_key] = value.to_s
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
end
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Kube
|
|
4
|
-
module Cluster
|
|
5
|
-
class Manifest < Kube::Schema::Manifest
|
|
6
|
-
class Middleware
|
|
7
|
-
# Sets +metadata.namespace+ on all namespace-scoped resources.
|
|
8
|
-
# Cluster-scoped kinds (Namespace, ClusterRole, etc.) are skipped.
|
|
9
|
-
#
|
|
10
|
-
# stack do
|
|
11
|
-
# use Middleware::Namespace, "production"
|
|
12
|
-
# end
|
|
13
|
-
#
|
|
14
|
-
class Namespace < Middleware
|
|
15
|
-
def initialize(namespace)
|
|
16
|
-
@namespace = namespace
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
def call(resource)
|
|
20
|
-
return resource if cluster_scoped?(resource)
|
|
21
|
-
|
|
22
|
-
h = resource.to_h
|
|
23
|
-
h[:metadata] ||= {}
|
|
24
|
-
h[:metadata][:namespace] = @namespace
|
|
25
|
-
rebuild(resource, h)
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
end
|