feat: cloud-init supports all documented KubeSolo CLI flags
Add missing flags (--local-storage-shared-path, --debug, --pprof-server, --portainer-edge-id, --portainer-edge-key, --portainer-edge-async) so all 10 documented KubeSolo parameters can be configured via cloud-init YAML. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,13 @@ All notable changes to KubeSolo OS are documented in this file.
|
|||||||
Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||||
versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Cloud-init: support all documented KubeSolo CLI flags (`--local-storage-shared-path`, `--debug`, `--pprof-server`, `--portainer-edge-id`, `--portainer-edge-key`, `--portainer-edge-async`)
|
||||||
|
- Cloud-init: `full-config.yaml` example showing all supported parameters
|
||||||
|
- Cloud-init: KubeSolo configuration reference table in docs/cloud-init.md
|
||||||
|
|
||||||
## [0.1.0] - 2026-02-12
|
## [0.1.0] - 2026-02-12
|
||||||
|
|
||||||
First release with all 5 design-doc phases complete. ISO boots and runs K8s pods.
|
First release with all 5 design-doc phases complete. ISO boots and runs K8s pods.
|
||||||
|
|||||||
18
README.md
18
README.md
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
An immutable, bootable Linux distribution purpose-built for [KubeSolo](https://github.com/portainer/kubesolo) — Portainer's ultra-lightweight single-node Kubernetes.
|
An immutable, bootable Linux distribution purpose-built for [KubeSolo](https://github.com/portainer/kubesolo) — Portainer's ultra-lightweight single-node Kubernetes.
|
||||||
|
|
||||||
> **Status:** All 5 phases complete. Boots and runs K8s workloads. Portainer Edge Agent tested and connected.
|
> **Status:** All 6 phases complete. Boots and runs K8s workloads. Portainer Edge Agent tested and connected.
|
||||||
|
|
||||||
## What is this?
|
## What is this?
|
||||||
|
|
||||||
@@ -122,7 +122,7 @@ Unnecessary subsystems (sound, GPU, wireless, Bluetooth, etc.) are stripped to k
|
|||||||
|
|
||||||
## Cloud-Init
|
## Cloud-Init
|
||||||
|
|
||||||
First-boot configuration via a simple YAML schema:
|
First-boot configuration via a simple YAML schema. All [documented KubeSolo flags](https://www.kubesolo.io/documentation#install) are supported:
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
hostname: edge-node-01
|
hostname: edge-node-01
|
||||||
@@ -133,10 +133,15 @@ network:
|
|||||||
dns:
|
dns:
|
||||||
- 8.8.8.8
|
- 8.8.8.8
|
||||||
kubesolo:
|
kubesolo:
|
||||||
node-name: edge-node-01
|
local-storage: true
|
||||||
portainer:
|
local-storage-shared-path: "/mnt/shared"
|
||||||
edge_id: "your-edge-id"
|
apiserver-extra-sans:
|
||||||
edge_key: "your-edge-key"
|
- edge-node-01.local
|
||||||
|
debug: false
|
||||||
|
pprof-server: false
|
||||||
|
portainer-edge-id: "your-edge-id"
|
||||||
|
portainer-edge-key: "your-edge-key"
|
||||||
|
portainer-edge-async: true
|
||||||
```
|
```
|
||||||
|
|
||||||
See [docs/cloud-init.md](docs/cloud-init.md) and the [examples](cloud-init/examples/).
|
See [docs/cloud-init.md](docs/cloud-init.md) and the [examples](cloud-init/examples/).
|
||||||
@@ -227,6 +232,7 @@ Metrics include: `kubesolo_os_info`, `boot_success`, `boot_counter`, `uptime_sec
|
|||||||
| 3 | A/B atomic updates, GRUB, rollback agent | Complete |
|
| 3 | A/B atomic updates, GRUB, rollback agent | Complete |
|
||||||
| 4 | Ed25519 signing, Portainer Edge, SSH extension | Complete |
|
| 4 | Ed25519 signing, Portainer Edge, SSH extension | Complete |
|
||||||
| 5 | CI/CD, OCI distribution, Prometheus metrics, ARM64 | Complete |
|
| 5 | CI/CD, OCI distribution, Prometheus metrics, ARM64 | Complete |
|
||||||
|
| 6 | Security hardening, AppArmor, ARM64 RPi support | Complete |
|
||||||
| - | Custom kernel build for container runtime fixes | Complete |
|
| - | Custom kernel build for container runtime fixes | Complete |
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|||||||
@@ -31,9 +31,15 @@ type NetworkConfig struct {
|
|||||||
|
|
||||||
// KubeSoloConfig defines KubeSolo-specific settings.
|
// KubeSoloConfig defines KubeSolo-specific settings.
|
||||||
type KubeSoloConfig struct {
|
type KubeSoloConfig struct {
|
||||||
ExtraFlags string `yaml:"extra-flags"`
|
ExtraFlags string `yaml:"extra-flags"`
|
||||||
LocalStorage *bool `yaml:"local-storage"`
|
LocalStorage *bool `yaml:"local-storage"`
|
||||||
ExtraSANs []string `yaml:"apiserver-extra-sans"`
|
LocalStorageSharedPath string `yaml:"local-storage-shared-path"`
|
||||||
|
ExtraSANs []string `yaml:"apiserver-extra-sans"`
|
||||||
|
Debug bool `yaml:"debug"`
|
||||||
|
PprofServer bool `yaml:"pprof-server"`
|
||||||
|
PortainerEdgeID string `yaml:"portainer-edge-id"`
|
||||||
|
PortainerEdgeKey string `yaml:"portainer-edge-key"`
|
||||||
|
PortainerEdgeAsync bool `yaml:"portainer-edge-async"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NTPConfig defines NTP settings.
|
// NTPConfig defines NTP settings.
|
||||||
|
|||||||
40
cloud-init/examples/full-config.yaml
Normal file
40
cloud-init/examples/full-config.yaml
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
# KubeSolo OS Cloud-Init — Full Configuration Reference
|
||||||
|
# Shows ALL supported KubeSolo parameters.
|
||||||
|
# Place at: /mnt/data/etc-kubesolo/cloud-init.yaml (on data partition)
|
||||||
|
# Or pass via boot param: kubesolo.cloudinit=/path/to/this.yaml
|
||||||
|
|
||||||
|
hostname: kubesolo-edge-01
|
||||||
|
|
||||||
|
network:
|
||||||
|
mode: dhcp
|
||||||
|
# interface: eth0 # Optional: specify interface (auto-detected if omitted)
|
||||||
|
# dns: # Optional: override DHCP-provided DNS
|
||||||
|
# - 8.8.8.8
|
||||||
|
|
||||||
|
kubesolo:
|
||||||
|
# Enable local-path-provisioner for persistent volumes (default: true)
|
||||||
|
local-storage: true
|
||||||
|
|
||||||
|
# Shared path for local-path-provisioner storage
|
||||||
|
local-storage-shared-path: "/mnt/shared"
|
||||||
|
|
||||||
|
# Extra SANs for API server TLS certificate
|
||||||
|
apiserver-extra-sans:
|
||||||
|
- kubesolo-edge-01.local
|
||||||
|
- 192.168.1.100
|
||||||
|
|
||||||
|
# Enable verbose debug logging
|
||||||
|
debug: false
|
||||||
|
|
||||||
|
# Enable Go pprof profiling server
|
||||||
|
pprof-server: false
|
||||||
|
|
||||||
|
# Portainer Edge Agent connection (alternative to portainer.edge-agent section)
|
||||||
|
# These generate --portainer-edge-id, --portainer-edge-key, --portainer-edge-async
|
||||||
|
# CLI flags for KubeSolo's built-in Edge Agent support.
|
||||||
|
portainer-edge-id: "your-edge-id"
|
||||||
|
portainer-edge-key: "your-edge-key"
|
||||||
|
portainer-edge-async: true
|
||||||
|
|
||||||
|
# Arbitrary extra flags passed directly to the KubeSolo binary
|
||||||
|
# extra-flags: "--disable traefik --disable servicelb"
|
||||||
@@ -46,6 +46,30 @@ func buildExtraFlags(cfg *Config) string {
|
|||||||
parts = append(parts, "--apiserver-extra-sans", san)
|
parts = append(parts, "--apiserver-extra-sans", san)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cfg.KubeSolo.LocalStorageSharedPath != "" {
|
||||||
|
parts = append(parts, "--local-storage-shared-path", cfg.KubeSolo.LocalStorageSharedPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.KubeSolo.Debug {
|
||||||
|
parts = append(parts, "--debug")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.KubeSolo.PprofServer {
|
||||||
|
parts = append(parts, "--pprof-server")
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.KubeSolo.PortainerEdgeID != "" {
|
||||||
|
parts = append(parts, "--portainer-edge-id", cfg.KubeSolo.PortainerEdgeID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.KubeSolo.PortainerEdgeKey != "" {
|
||||||
|
parts = append(parts, "--portainer-edge-key", cfg.KubeSolo.PortainerEdgeKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.KubeSolo.PortainerEdgeAsync {
|
||||||
|
parts = append(parts, "--portainer-edge-async")
|
||||||
|
}
|
||||||
|
|
||||||
return strings.Join(parts, " ")
|
return strings.Join(parts, " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,54 @@ func TestBuildExtraFlags(t *testing.T) {
|
|||||||
},
|
},
|
||||||
want: "--disable servicelb --apiserver-extra-sans edge.local",
|
want: "--disable servicelb --apiserver-extra-sans edge.local",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "debug flag",
|
||||||
|
cfg: Config{
|
||||||
|
KubeSolo: KubeSoloConfig{Debug: true},
|
||||||
|
},
|
||||||
|
want: "--debug",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pprof-server flag",
|
||||||
|
cfg: Config{
|
||||||
|
KubeSolo: KubeSoloConfig{PprofServer: true},
|
||||||
|
},
|
||||||
|
want: "--pprof-server",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "local-storage-shared-path",
|
||||||
|
cfg: Config{
|
||||||
|
KubeSolo: KubeSoloConfig{LocalStorageSharedPath: "/mnt/shared"},
|
||||||
|
},
|
||||||
|
want: "--local-storage-shared-path /mnt/shared",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "portainer edge flags",
|
||||||
|
cfg: Config{
|
||||||
|
KubeSolo: KubeSoloConfig{
|
||||||
|
PortainerEdgeID: "test-id-123",
|
||||||
|
PortainerEdgeKey: "test-key-456",
|
||||||
|
PortainerEdgeAsync: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: "--portainer-edge-id test-id-123 --portainer-edge-key test-key-456 --portainer-edge-async",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all new flags",
|
||||||
|
cfg: Config{
|
||||||
|
KubeSolo: KubeSoloConfig{
|
||||||
|
ExtraFlags: "--disable traefik",
|
||||||
|
ExtraSANs: []string{"node.local"},
|
||||||
|
LocalStorageSharedPath: "/mnt/data/shared",
|
||||||
|
Debug: true,
|
||||||
|
PprofServer: true,
|
||||||
|
PortainerEdgeID: "eid",
|
||||||
|
PortainerEdgeKey: "ekey",
|
||||||
|
PortainerEdgeAsync: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: "--disable traefik --apiserver-extra-sans node.local --local-storage-shared-path /mnt/data/shared --debug --pprof-server --portainer-edge-id eid --portainer-edge-key ekey --portainer-edge-async",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
@@ -61,9 +109,14 @@ func TestApplyKubeSolo(t *testing.T) {
|
|||||||
tr := true
|
tr := true
|
||||||
cfg := &Config{
|
cfg := &Config{
|
||||||
KubeSolo: KubeSoloConfig{
|
KubeSolo: KubeSoloConfig{
|
||||||
ExtraFlags: "--disable traefik",
|
ExtraFlags: "--disable traefik",
|
||||||
LocalStorage: &tr,
|
LocalStorage: &tr,
|
||||||
ExtraSANs: []string{"test.local"},
|
ExtraSANs: []string{"test.local"},
|
||||||
|
LocalStorageSharedPath: "/mnt/shared",
|
||||||
|
Debug: true,
|
||||||
|
PortainerEdgeID: "eid",
|
||||||
|
PortainerEdgeKey: "ekey",
|
||||||
|
PortainerEdgeAsync: true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,6 +136,21 @@ func TestApplyKubeSolo(t *testing.T) {
|
|||||||
if !strings.Contains(flags, "--apiserver-extra-sans test.local") {
|
if !strings.Contains(flags, "--apiserver-extra-sans test.local") {
|
||||||
t.Errorf("extra-flags missing SANs: %q", flags)
|
t.Errorf("extra-flags missing SANs: %q", flags)
|
||||||
}
|
}
|
||||||
|
if !strings.Contains(flags, "--local-storage-shared-path /mnt/shared") {
|
||||||
|
t.Errorf("extra-flags missing local-storage-shared-path: %q", flags)
|
||||||
|
}
|
||||||
|
if !strings.Contains(flags, "--debug") {
|
||||||
|
t.Errorf("extra-flags missing --debug: %q", flags)
|
||||||
|
}
|
||||||
|
if !strings.Contains(flags, "--portainer-edge-id eid") {
|
||||||
|
t.Errorf("extra-flags missing --portainer-edge-id: %q", flags)
|
||||||
|
}
|
||||||
|
if !strings.Contains(flags, "--portainer-edge-key ekey") {
|
||||||
|
t.Errorf("extra-flags missing --portainer-edge-key: %q", flags)
|
||||||
|
}
|
||||||
|
if !strings.Contains(flags, "--portainer-edge-async") {
|
||||||
|
t.Errorf("extra-flags missing --portainer-edge-async: %q", flags)
|
||||||
|
}
|
||||||
|
|
||||||
// Check config.yaml
|
// Check config.yaml
|
||||||
configData, err := os.ReadFile(filepath.Join(dir, "config.yaml"))
|
configData, err := os.ReadFile(filepath.Join(dir, "config.yaml"))
|
||||||
|
|||||||
@@ -225,6 +225,7 @@ func TestParseExampleFiles(t *testing.T) {
|
|||||||
"examples/static-ip.yaml",
|
"examples/static-ip.yaml",
|
||||||
"examples/portainer-edge.yaml",
|
"examples/portainer-edge.yaml",
|
||||||
"examples/airgapped.yaml",
|
"examples/airgapped.yaml",
|
||||||
|
"examples/full-config.yaml",
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, path := range examples {
|
for _, path := range examples {
|
||||||
|
|||||||
@@ -45,9 +45,15 @@ network:
|
|||||||
kubesolo:
|
kubesolo:
|
||||||
extra-flags: "--disable traefik" # Extra CLI flags for KubeSolo binary
|
extra-flags: "--disable traefik" # Extra CLI flags for KubeSolo binary
|
||||||
local-storage: true # Enable local-path provisioner (default: true)
|
local-storage: true # Enable local-path provisioner (default: true)
|
||||||
|
local-storage-shared-path: "/mnt/shared" # Shared path for local-path-provisioner
|
||||||
apiserver-extra-sans: # Extra SANs for API server certificate
|
apiserver-extra-sans: # Extra SANs for API server certificate
|
||||||
- node.example.com
|
- node.example.com
|
||||||
- 10.0.0.50
|
- 10.0.0.50
|
||||||
|
debug: false # Enable verbose debug logging
|
||||||
|
pprof-server: false # Enable Go pprof profiling server
|
||||||
|
portainer-edge-id: "" # Portainer Edge Agent ID
|
||||||
|
portainer-edge-key: "" # Portainer Edge Agent key
|
||||||
|
portainer-edge-async: false # Enable async Portainer Edge communication
|
||||||
|
|
||||||
# NTP servers (optional)
|
# NTP servers (optional)
|
||||||
ntp:
|
ntp:
|
||||||
@@ -129,6 +135,24 @@ kubesolo-cloudinit validate /path/to/cloud-init.yaml
|
|||||||
kubesolo-cloudinit dump /path/to/cloud-init.yaml
|
kubesolo-cloudinit dump /path/to/cloud-init.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## KubeSolo Configuration Reference
|
||||||
|
|
||||||
|
All fields under the `kubesolo:` section and their corresponding CLI flags:
|
||||||
|
|
||||||
|
| YAML Field | CLI Flag | Type | Default | Description |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| `extra-flags` | (raw flags) | string | `""` | Arbitrary extra flags passed to KubeSolo binary |
|
||||||
|
| `local-storage` | `--local-storage` | bool | `true` | Enable local-path-provisioner for PVCs |
|
||||||
|
| `local-storage-shared-path` | `--local-storage-shared-path` | string | `""` | Shared path for local-path-provisioner storage |
|
||||||
|
| `apiserver-extra-sans` | `--apiserver-extra-sans` | list | `[]` | Extra SANs for API server TLS certificate |
|
||||||
|
| `debug` | `--debug` | bool | `false` | Enable verbose debug logging |
|
||||||
|
| `pprof-server` | `--pprof-server` | bool | `false` | Enable Go pprof profiling server |
|
||||||
|
| `portainer-edge-id` | `--portainer-edge-id` | string | `""` | Portainer Edge Agent ID (from Portainer UI) |
|
||||||
|
| `portainer-edge-key` | `--portainer-edge-key` | string | `""` | Portainer Edge Agent key (from Portainer UI) |
|
||||||
|
| `portainer-edge-async` | `--portainer-edge-async` | bool | `false` | Enable async Portainer Edge communication |
|
||||||
|
|
||||||
|
**Note:** The `portainer-edge-*` fields generate CLI flags for KubeSolo's built-in Edge Agent support. This is an alternative to the `portainer.edge-agent` section, which creates a standalone Kubernetes manifest. Use one approach or the other, not both.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
See `cloud-init/examples/` for complete configuration examples:
|
See `cloud-init/examples/` for complete configuration examples:
|
||||||
@@ -137,6 +161,7 @@ See `cloud-init/examples/` for complete configuration examples:
|
|||||||
- `static-ip.yaml` — Static IP configuration
|
- `static-ip.yaml` — Static IP configuration
|
||||||
- `portainer-edge.yaml` — Portainer Edge Agent integration
|
- `portainer-edge.yaml` — Portainer Edge Agent integration
|
||||||
- `airgapped.yaml` — Air-gapped deployment with pre-loaded images
|
- `airgapped.yaml` — Air-gapped deployment with pre-loaded images
|
||||||
|
- `full-config.yaml` — All supported KubeSolo parameters
|
||||||
|
|
||||||
## Building
|
## Building
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user