kube_cluster 0.2.0 → 0.2.1
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/.github/workflows/release.yml +43 -0
- data/.github/workflows/tag-gem-version-bump.yml +47 -0
- data/.gitignore +2 -0
- data/Gemfile.lock +48 -52
- data/bin/console +3 -0
- data/bin/dev +4 -0
- data/docker-compose.yml +26 -0
- data/examples/01-basic-redis-pod/manifest.rb +60 -0
- data/examples/database/manifest.rb +238 -0
- data/examples/version2/demo.rb +87 -0
- data/examples/version2/helpers.rb +18 -0
- data/examples/version2/my_app.rb +45 -0
- data/examples/version2/postgresql.rb +81 -0
- data/examples/version2/ruby_on_rails.rb +31 -0
- data/examples/web-app/manifest.rb +215 -0
- data/flake.lock +3 -3
- data/flake.nix +6 -0
- data/kube_cluster.gemspec +3 -1
- data/lib/kube/cli/cluster.rb +41 -0
- data/lib/kube/cluster/connection.rb +18 -0
- data/lib/kube/cluster/instance.rb +21 -0
- data/lib/kube/cluster/manifest/middleware/annotations.rb +32 -0
- data/lib/kube/cluster/manifest/middleware/hpa_for_deployment.rb +109 -0
- data/lib/kube/cluster/manifest/middleware/ingress_for_service.rb +89 -0
- data/lib/kube/cluster/manifest/middleware/labels.rb +59 -0
- data/lib/kube/cluster/manifest/middleware/namespace.rb +31 -0
- data/lib/kube/cluster/manifest/middleware/pod_anti_affinity.rb +61 -0
- data/lib/kube/cluster/manifest/middleware/resource_preset.rb +64 -0
- data/lib/kube/cluster/manifest/middleware/security_context.rb +84 -0
- data/lib/kube/cluster/manifest/middleware/service_for_deployment.rb +69 -0
- data/lib/kube/cluster/manifest/middleware.rb +178 -0
- data/lib/kube/cluster/manifest/stack.rb +56 -0
- data/lib/kube/cluster/manifest.rb +76 -0
- data/lib/kube/cluster/resource/dirty_tracking.rb +113 -0
- data/lib/kube/cluster/resource/persistence.rb +67 -0
- data/lib/kube/cluster/resource.rb +21 -0
- data/lib/kube/cluster/version.rb +1 -1
- data/lib/kube/cluster.rb +13 -7
- data/lib/kube/errors.rb +57 -0
- metadata +63 -17
- data/Rakefile +0 -11
- data/TREE_PLAN.md +0 -513
- data/bin/generate-command-schema-v1 +0 -44
- data/data/kubectl-command-tree-v1-minimal.json +0 -125
- data/data/kubectl-command-tree-v1.json +0 -1469
- data/examples/quick-repl/docker-compose.yml +0 -52
- data/exe/kube_cluster +0 -6
- data/lib/kube/cluster/command_node.rb +0 -89
- data/lib/kube/cluster/ctl.rb +0 -33
- data/lib/kube/cluster/query_builder.rb +0 -35
- data/lib/kube/cluster/resource_selector.rb +0 -19
- data/lib/kube/cluster/tree_node.rb +0 -51
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f6b2fcc6e1de8c59841b080e8150aa249d84fc064f4eab2e981be9ded9dc2290
|
|
4
|
+
data.tar.gz: 1a55810edfb5809b44699e2a17984b9f0311257aba52796da6b74e293ce3a9d0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b64081cec15ce53b3a80effecba8f2ae3ec43ee58d1dc483196a821f37a4d6aa0aabbdd3d21e5df7eb5208cbd8bdbcf1aefbf82a9911733795b5ad4689c2c62e
|
|
7
|
+
data.tar.gz: ef610d9b33d6907a92f69c077080a8f26ffd6a1e2f2e391b9f54564187b6799af2e275b2f9f6cb3b056f3f9b3952b964921692a2d4456ee5ee7af229b209ac0c
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- 'v*'
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
build-and-push-gem:
|
|
13
|
+
uses: ./.github/workflows/build-and-push-gem.yml
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
id-token: write
|
|
17
|
+
secrets: inherit
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
github-release:
|
|
22
|
+
|
|
23
|
+
needs: [build-and-push-gem]
|
|
24
|
+
|
|
25
|
+
runs-on: ubuntu-24.04
|
|
26
|
+
permissions:
|
|
27
|
+
contents: write
|
|
28
|
+
steps:
|
|
29
|
+
- uses: actions/download-artifact@v4
|
|
30
|
+
with:
|
|
31
|
+
path: release/
|
|
32
|
+
merge-multiple: true
|
|
33
|
+
|
|
34
|
+
- name: Consolidate checksums
|
|
35
|
+
run: |
|
|
36
|
+
cd release
|
|
37
|
+
cat SHA256SUMS-linux-*.txt > SHA256SUMS.txt
|
|
38
|
+
rm SHA256SUMS-linux-*.txt
|
|
39
|
+
|
|
40
|
+
- uses: softprops/action-gh-release@v2
|
|
41
|
+
with:
|
|
42
|
+
files: release/*
|
|
43
|
+
generate_release_notes: true
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
name: Auto-tag on version bump
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
tag:
|
|
9
|
+
runs-on: ubuntu-24.04
|
|
10
|
+
permissions:
|
|
11
|
+
contents: write
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
with:
|
|
15
|
+
fetch-depth: 0
|
|
16
|
+
|
|
17
|
+
- uses: ruby/setup-ruby@v1
|
|
18
|
+
with:
|
|
19
|
+
ruby-version: '3.4'
|
|
20
|
+
|
|
21
|
+
- name: Read version from gemspec
|
|
22
|
+
id: read
|
|
23
|
+
run: |
|
|
24
|
+
version=$(ruby -e '
|
|
25
|
+
spec = Gem::Specification.load(Dir["*.gemspec"].first)
|
|
26
|
+
puts spec.version
|
|
27
|
+
')
|
|
28
|
+
echo "version=$version" >> "$GITHUB_OUTPUT"
|
|
29
|
+
|
|
30
|
+
- name: Check if tag exists
|
|
31
|
+
id: check
|
|
32
|
+
run: |
|
|
33
|
+
tag="v${{ steps.read.outputs.version }}"
|
|
34
|
+
if git rev-parse "$tag" >/dev/null 2>&1; then
|
|
35
|
+
echo "exists=true" >> "$GITHUB_OUTPUT"
|
|
36
|
+
else
|
|
37
|
+
echo "exists=false" >> "$GITHUB_OUTPUT"
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
- name: Create and push tag
|
|
41
|
+
if: steps.check.outputs.exists == 'false'
|
|
42
|
+
run: |
|
|
43
|
+
tag="v${{ steps.read.outputs.version }}"
|
|
44
|
+
git config user.name "Nathan K"
|
|
45
|
+
git config user.email "nathankidd@hey.com"
|
|
46
|
+
git tag -a "$tag" -m "Release $tag"
|
|
47
|
+
git push origin "$tag"
|
data/.gitignore
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,52 +1,75 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
kube_cluster (0.
|
|
5
|
-
|
|
4
|
+
kube_cluster (0.2.0)
|
|
5
|
+
kube_kit (> 0)
|
|
6
|
+
kube_kubectl (~> 2.0.0)
|
|
7
|
+
kube_schema (~> 1.2.0)
|
|
6
8
|
|
|
7
9
|
GEM
|
|
8
10
|
remote: https://rubygems.org/
|
|
9
11
|
specs:
|
|
10
|
-
addressable (2.8.9)
|
|
11
|
-
public_suffix (>= 2.0.2, < 8.0)
|
|
12
12
|
ast (2.4.3)
|
|
13
|
-
bigdecimal (4.
|
|
13
|
+
bigdecimal (4.1.2)
|
|
14
14
|
black_hole_struct (0.1.3)
|
|
15
|
+
date (3.5.1)
|
|
16
|
+
debug (1.11.1)
|
|
17
|
+
irb (~> 1.10)
|
|
18
|
+
reline (>= 0.3.8)
|
|
19
|
+
erb (6.0.3)
|
|
15
20
|
hana (1.3.7)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
21
|
+
io-console (0.8.2)
|
|
22
|
+
irb (1.17.0)
|
|
23
|
+
pp (>= 0.6.0)
|
|
24
|
+
prism (>= 1.3.0)
|
|
25
|
+
rdoc (>= 4.0.0)
|
|
26
|
+
reline (>= 0.4.2)
|
|
27
|
+
json (2.19.3)
|
|
20
28
|
json_schemer (2.5.0)
|
|
21
29
|
bigdecimal
|
|
22
30
|
hana (~> 1.3)
|
|
23
31
|
regexp_parser (~> 2.0)
|
|
24
32
|
simpleidn (~> 0.2)
|
|
25
|
-
|
|
33
|
+
kube_kit (0.2.0)
|
|
34
|
+
kube_kubectl (2.0.1)
|
|
35
|
+
debug (~> 1.11)
|
|
36
|
+
json_schemer (~> 2.5)
|
|
37
|
+
rubyshell (~> 1.5)
|
|
38
|
+
shellwords (~> 0.2.2)
|
|
39
|
+
string_builder (~> 1.2.0)
|
|
40
|
+
kube_schema (1.2.1)
|
|
26
41
|
black_hole_struct (~> 0.1)
|
|
27
42
|
json_schemer (~> 2.5)
|
|
28
43
|
rubyshell (~> 1.5)
|
|
29
44
|
language_server-protocol (3.17.0.5)
|
|
30
45
|
lint_roller (1.1.0)
|
|
31
|
-
mcp (0.8.0)
|
|
32
|
-
json-schema (>= 4.1)
|
|
33
46
|
minitest (5.27.0)
|
|
34
|
-
parallel (
|
|
35
|
-
parser (3.3.
|
|
47
|
+
parallel (2.0.1)
|
|
48
|
+
parser (3.3.11.1)
|
|
36
49
|
ast (~> 2.4.1)
|
|
37
50
|
racc
|
|
51
|
+
pp (0.6.3)
|
|
52
|
+
prettyprint
|
|
53
|
+
prettyprint (0.2.0)
|
|
38
54
|
prism (1.9.0)
|
|
39
|
-
|
|
55
|
+
psych (5.3.1)
|
|
56
|
+
date
|
|
57
|
+
stringio
|
|
40
58
|
racc (1.8.1)
|
|
41
59
|
rainbow (3.1.1)
|
|
42
|
-
rake (13.
|
|
43
|
-
|
|
44
|
-
|
|
60
|
+
rake (13.4.2)
|
|
61
|
+
rdoc (7.2.0)
|
|
62
|
+
erb
|
|
63
|
+
psych (>= 4.0.0)
|
|
64
|
+
tsort
|
|
65
|
+
regexp_parser (2.12.0)
|
|
66
|
+
reline (0.6.3)
|
|
67
|
+
io-console (~> 0.5)
|
|
68
|
+
rubocop (1.86.1)
|
|
45
69
|
json (~> 2.3)
|
|
46
70
|
language_server-protocol (~> 3.17.0.2)
|
|
47
71
|
lint_roller (~> 1.1.0)
|
|
48
|
-
|
|
49
|
-
parallel (~> 1.10)
|
|
72
|
+
parallel (>= 1.10)
|
|
50
73
|
parser (>= 3.3.0.2)
|
|
51
74
|
rainbow (>= 2.2.2, < 4.0)
|
|
52
75
|
regexp_parser (>= 2.9.3, < 3.0)
|
|
@@ -58,7 +81,11 @@ GEM
|
|
|
58
81
|
prism (~> 1.7)
|
|
59
82
|
ruby-progressbar (1.13.0)
|
|
60
83
|
rubyshell (1.5.0)
|
|
84
|
+
shellwords (0.2.2)
|
|
61
85
|
simpleidn (0.2.3)
|
|
86
|
+
string_builder (1.2.0)
|
|
87
|
+
stringio (3.2.0)
|
|
88
|
+
tsort (0.2.0)
|
|
62
89
|
unicode-display_width (3.2.0)
|
|
63
90
|
unicode-emoji (~> 4.1)
|
|
64
91
|
unicode-emoji (4.2.0)
|
|
@@ -73,36 +100,5 @@ DEPENDENCIES
|
|
|
73
100
|
rake (~> 13.0)
|
|
74
101
|
rubocop (~> 1.21)
|
|
75
102
|
|
|
76
|
-
CHECKSUMS
|
|
77
|
-
addressable (2.8.9) sha256=cc154fcbe689711808a43601dee7b980238ce54368d23e127421753e46895485
|
|
78
|
-
ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
|
|
79
|
-
bigdecimal (4.0.1) sha256=8b07d3d065a9f921c80ceaea7c9d4ae596697295b584c296fe599dd0ad01c4a7
|
|
80
|
-
black_hole_struct (0.1.3) sha256=b1cac7dbe7f36bb3ed8372de656dbe140ad20d786aaace552c5706f7aa46c4a3
|
|
81
|
-
hana (1.3.7) sha256=5425db42d651fea08859811c29d20446f16af196308162894db208cac5ce9b0d
|
|
82
|
-
json (2.19.2) sha256=e7e1bd318b2c37c4ceee2444841c86539bc462e81f40d134cf97826cb14e83cf
|
|
83
|
-
json-schema (6.2.0) sha256=e8bff46ed845a22c1ab2bd0d7eccf831c01fe23bb3920caa4c74db4306813666
|
|
84
|
-
json_schemer (2.5.0) sha256=2f01fb4cce721a4e08dd068fc2030cffd0702a7f333f1ea2be6e8991f00ae396
|
|
85
|
-
kube_cluster (0.1.1)
|
|
86
|
-
kube_schema (1.0.0) sha256=a83e584b316f21492fe551231f22cf3e7439fd6f1df6f3769c24d66ab040dc6e
|
|
87
|
-
language_server-protocol (3.17.0.5) sha256=fd1e39a51a28bf3eec959379985a72e296e9f9acfce46f6a79d31ca8760803cc
|
|
88
|
-
lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
|
|
89
|
-
mcp (0.8.0) sha256=ae8bd146bb8e168852866fd26f805f52744f6326afb3211e073f78a95e0c34fb
|
|
90
|
-
minitest (5.27.0) sha256=2d3b17f8a36fe7801c1adcffdbc38233b938eb0b4966e97a6739055a45fa77d5
|
|
91
|
-
parallel (1.27.0) sha256=4ac151e1806b755fb4e2dc2332cbf0e54f2e24ba821ff2d3dcf86bf6dc4ae130
|
|
92
|
-
parser (3.3.10.2) sha256=6f60c84aa4bdcedb6d1a2434b738fe8a8136807b6adc8f7f53b97da9bc4e9357
|
|
93
|
-
prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85
|
|
94
|
-
public_suffix (7.0.5) sha256=1a8bb08f1bbea19228d3bed6e5ed908d1cb4f7c2726d18bd9cadf60bc676f623
|
|
95
|
-
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
|
|
96
|
-
rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
|
|
97
|
-
rake (13.3.1) sha256=8c9e89d09f66a26a01264e7e3480ec0607f0c497a861ef16063604b1b08eb19c
|
|
98
|
-
regexp_parser (2.11.3) sha256=ca13f381a173b7a93450e53459075c9b76a10433caadcb2f1180f2c741fc55a4
|
|
99
|
-
rubocop (1.85.1) sha256=3dbcf9e961baa4c376eeeb2a03913dca5e3987033b04d38fa538aa1e7406cc77
|
|
100
|
-
rubocop-ast (1.49.1) sha256=4412f3ee70f6fe4546cc489548e0f6fcf76cafcfa80fa03af67098ffed755035
|
|
101
|
-
ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33
|
|
102
|
-
rubyshell (1.5.0) sha256=ffd528415962e52b2f3ec155fc3bc2cac401981413c0db451ea2a20194916ab6
|
|
103
|
-
simpleidn (0.2.3) sha256=08ce96f03fa1605286be22651ba0fc9c0b2d6272c9b27a260bc88be05b0d2c29
|
|
104
|
-
unicode-display_width (3.2.0) sha256=0cdd96b5681a5949cdbc2c55e7b420facae74c4aaf9a9815eee1087cb1853c42
|
|
105
|
-
unicode-emoji (4.2.0) sha256=519e69150f75652e40bf736106cfbc8f0f73aa3fb6a65afe62fefa7f80b0f80f
|
|
106
|
-
|
|
107
103
|
BUNDLED WITH
|
|
108
|
-
|
|
104
|
+
2.6.9
|
data/bin/console
CHANGED
data/bin/dev
ADDED
data/docker-compose.yml
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# to run define K3S_TOKEN, K3S_VERSION is optional, eg:
|
|
2
|
+
# K3S_TOKEN=${RANDOM}${RANDOM}${RANDOM} docker-compose up
|
|
3
|
+
services:
|
|
4
|
+
server:
|
|
5
|
+
image: "rancher/k3s:latest"
|
|
6
|
+
command: server
|
|
7
|
+
tmpfs:
|
|
8
|
+
- /run
|
|
9
|
+
- /var/run
|
|
10
|
+
ulimits:
|
|
11
|
+
nproc: 65535
|
|
12
|
+
nofile:
|
|
13
|
+
soft: 65535
|
|
14
|
+
hard: 65535
|
|
15
|
+
privileged: true
|
|
16
|
+
restart: always
|
|
17
|
+
environment:
|
|
18
|
+
- K3S_TOKEN=change-me-or-face-the-consequences
|
|
19
|
+
- K3S_KUBECONFIG_OUTPUT=/output/kubeconfig.yaml
|
|
20
|
+
- K3S_KUBECONFIG_MODE=666
|
|
21
|
+
volumes:
|
|
22
|
+
- .:/output
|
|
23
|
+
ports:
|
|
24
|
+
- 6443:6443 # Kubernetes API Server
|
|
25
|
+
- 80:80 # Ingress controller port 80
|
|
26
|
+
- 443:443 # Ingress controller port 443
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
require "bundler/setup"
|
|
2
|
+
require "kube/schema"
|
|
3
|
+
|
|
4
|
+
class RedisPod < Kube::Schema['Pod']
|
|
5
|
+
def initialize(container_name: 'my-redis-container', **options, &block)
|
|
6
|
+
super {
|
|
7
|
+
spec.containers = [
|
|
8
|
+
{
|
|
9
|
+
name: container_name,
|
|
10
|
+
image: 'redis:8.0.2',
|
|
11
|
+
|
|
12
|
+
command: ["redis-server", "/redis-master/redis.conf"],
|
|
13
|
+
env: [{name: 'MASTER', value: "true"}],
|
|
14
|
+
ports: [{ containerPort: 6379 }],
|
|
15
|
+
|
|
16
|
+
resources: { limits: { cpu: "0.1" } },
|
|
17
|
+
volumeMounts: [
|
|
18
|
+
{ mountPath: '/redis-master-data', name: 'data' },
|
|
19
|
+
{ mountPath: '/redis-master', name: 'config' },
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
spec.volumes = [
|
|
25
|
+
{
|
|
26
|
+
name: 'data',
|
|
27
|
+
emptyDir: {}
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'config',
|
|
31
|
+
configMap: {
|
|
32
|
+
name: 'example-redis-config',
|
|
33
|
+
items: [ { key: 'redis-config', path: 'redis.conf' } ]
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
instance_exec(&block) if block_given?
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
puts RedisPod.new(
|
|
43
|
+
container_name: 'my-redis-container-1',
|
|
44
|
+
metadata: {
|
|
45
|
+
namespace: 'my-namespace'
|
|
46
|
+
}
|
|
47
|
+
).to_yaml
|
|
48
|
+
|
|
49
|
+
puts RedisPod.new(container_name: 'my-redis-container-1') {
|
|
50
|
+
metadata.namespace = 'my-namespace'
|
|
51
|
+
}.to_yaml
|
|
52
|
+
|
|
53
|
+
puts RedisPod.new {
|
|
54
|
+
metadata.namespace = "my-namespace"
|
|
55
|
+
}.to_yaml
|
|
56
|
+
|
|
57
|
+
puts RedisPod.new {
|
|
58
|
+
metadata.name = "my-redis-1"
|
|
59
|
+
metadata.namespace = "my-namespace"
|
|
60
|
+
}.to_yaml
|
|
@@ -0,0 +1,238 @@
|
|
|
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
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Middleware-driven Manifest Example
|
|
5
|
+
#
|
|
6
|
+
# Demonstrates how middleware eliminates boilerplate. The middleware stack
|
|
7
|
+
# declared in MyApp automatically generates Services, Ingresses, HPAs,
|
|
8
|
+
# and injects resource limits, security contexts, and pod anti-affinity.
|
|
9
|
+
#
|
|
10
|
+
# The block below only declares the unique intent — the things only a
|
|
11
|
+
# human knows. Everything else is derived by middleware from labels.
|
|
12
|
+
#
|
|
13
|
+
# What middleware generates from each Deployment:
|
|
14
|
+
# - Service (from container ports + matchLabels)
|
|
15
|
+
# - Ingress (from app.kubernetes.io/expose label)
|
|
16
|
+
# - HPA (from app.kubernetes.io/autoscale label)
|
|
17
|
+
# - Resource limits (from app.kubernetes.io/size label)
|
|
18
|
+
# - Security contexts (restricted profile, all pod-bearing resources)
|
|
19
|
+
# - Pod anti-affinity (spread across nodes, all pod-bearing resources)
|
|
20
|
+
# - Standard labels (managed-by, merged into everything)
|
|
21
|
+
#
|
|
22
|
+
# Usage:
|
|
23
|
+
# ruby examples/version2/demo.rb
|
|
24
|
+
# ruby examples/version2/demo.rb > app.yaml
|
|
25
|
+
|
|
26
|
+
require "kube/cluster"
|
|
27
|
+
require "securerandom"
|
|
28
|
+
require_relative "my_app"
|
|
29
|
+
|
|
30
|
+
app = MyApp.new("example.com", size: :small) do |m|
|
|
31
|
+
name = "rails-app"
|
|
32
|
+
ns = "production"
|
|
33
|
+
db_name = "postgresql"
|
|
34
|
+
db_ns = "database"
|
|
35
|
+
|
|
36
|
+
labels = m.app_labels(name: name, instance: name)
|
|
37
|
+
db_labels = m.app_labels(name: db_name, instance: db_name, component: "primary")
|
|
38
|
+
db_match = m.match_labels(name: db_name, instance: db_name, component: "primary")
|
|
39
|
+
|
|
40
|
+
# ── Rails tier ──────────────────────────────────────────────────
|
|
41
|
+
#
|
|
42
|
+
# One Namespace, one ConfigMap, one Deployment.
|
|
43
|
+
# Middleware generates: Service, Ingress, HPA
|
|
44
|
+
# Middleware injects: resource limits, security context, anti-affinity, labels
|
|
45
|
+
|
|
46
|
+
[
|
|
47
|
+
Kube::Schema["Namespace"].new {
|
|
48
|
+
metadata.name = ns
|
|
49
|
+
metadata.labels = labels
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
Kube::Schema["ConfigMap"].new {
|
|
53
|
+
metadata.name = "#{name}-config"
|
|
54
|
+
metadata.namespace = ns
|
|
55
|
+
metadata.labels = labels
|
|
56
|
+
self.data = {
|
|
57
|
+
RAILS_ENV: "production",
|
|
58
|
+
DATABASE_URL: "postgres://#{db_name}-headless.#{db_ns}.svc.cluster.local:5432/app",
|
|
59
|
+
LOG_LEVEL: "info",
|
|
60
|
+
WORKERS: "4",
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
RubyOnRails.new {
|
|
65
|
+
metadata.name = name
|
|
66
|
+
metadata.namespace = ns
|
|
67
|
+
metadata.labels = labels.merge(
|
|
68
|
+
"app.kubernetes.io/expose": "app.example.com",
|
|
69
|
+
"app.kubernetes.io/autoscale": "1-5",
|
|
70
|
+
)
|
|
71
|
+
},
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
# ── Database tier ───────────────────────────────────────────────
|
|
75
|
+
#
|
|
76
|
+
# StatefulSet + headless Service + Secret + NetworkPolicy.
|
|
77
|
+
# Middleware generates: Service (from container ports)
|
|
78
|
+
# Middleware injects: resource limits, security context, anti-affinity, labels
|
|
79
|
+
|
|
80
|
+
pg_password = SecureRandom.alphanumeric(24)
|
|
81
|
+
|
|
82
|
+
Postgresql.new {
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
puts app.to_yaml
|