Skip to content

Commit b8fa8d0

Browse files
authored
Merge branch 'develop' into feat/ks-52/prometheus-metrics-endpoint
2 parents 5a9c5f1 + 37b26c0 commit b8fa8d0

22 files changed

Lines changed: 164 additions & 29 deletions

File tree

INSTALL.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ export KUBESOLO_PATH="/var/lib/kubesolo" # Installation path
9898
export KUBESOLO_PORTAINER_EDGE_ID="your-id" # Portainer Edge ID
9999
export KUBESOLO_PORTAINER_EDGE_KEY="your-key" # Portainer Edge Key
100100
export KUBESOLO_PORTAINER_EDGE_ASYNC="false" # Async mode
101+
export KUBESOLO_PORTAINER_EDGE_IMAGE="docker.io/portainer/agent:lts" # Edge Agent image
101102
export KUBESOLO_LOCAL_STORAGE="false" # Enable local storage
102103
export KUBESOLO_DEBUG="false" # Debug logging
103104
export KUBESOLO_PPROF_SERVER="false" # Enable pprof

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ KubeSolo supports the following command-line flags:
159159
| `--portainer-edge-id` | `KUBESOLO_PORTAINER_EDGE_ID` | Portainer Edge ID | `""` |
160160
| `--portainer-edge-key` | `KUBESOLO_PORTAINER_EDGE_KEY` | Portainer Edge Key | `""` |
161161
| `--portainer-edge-async` | `KUBESOLO_PORTAINER_EDGE_ASYNC` | Enable Portainer Edge Async Mode | `false` |
162+
| `--portainer-edge-image` | `KUBESOLO_PORTAINER_EDGE_IMAGE` | Image deployed for the Portainer Edge Agent, including the tag. Any image other than the default is pulled from the registry rather than loaded from the embedded image | `docker.io/portainer/agent:lts` |
162163
| `--load-balancer` | `KUBESOLO_LOAD_BALANCER` | Enable load balancer. With this enabled, kubesolo will update a newly deployed service with the load balancer type so that the EXTERNAL-IP is set to the node IP | `true` |
163164
| `--local-storage` | `KUBESOLO_LOCAL_STORAGE` | Enable local storage | `true` |
164165
| `--local-storage-shared-path` | `KUBESOLO_LOCAL_STORAGE_SHARED_PATH` | Path to the shared file system for the local storage | `""` |

build/download-deps.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ ARCH="amd64"
88
CONTAINERD_VERSION="2.2.5"
99
CRUN_VERSION="1.26"
1010
CNI_VERSION="v1.9.0"
11-
PORTAINER_AGENT_VERSION="2.39.4"
11+
PORTAINER_AGENT_VERSION="lts"
1212
COREDNS_VERSION="1.14.4"
1313
LOCAL_PATH_PROVISIONER_VERSION="v0.0.36"
1414
PAUSE_IMAGE_VERSION="3.10"

cmd/kubesolo/main.go

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
"context"
5+
"fmt"
56
"os"
67
"os/signal"
78
"path/filepath"
@@ -11,6 +12,7 @@ import (
1112
"syscall"
1213

1314
"github.com/alecthomas/kingpin/v2"
15+
"github.com/distribution/reference"
1416
"github.com/portainer/kubesolo/internal/config/flags"
1517
"github.com/portainer/kubesolo/internal/core/embedded"
1618
"github.com/portainer/kubesolo/internal/core/pki"
@@ -48,6 +50,7 @@ type kubesolo struct {
4850
portainerEdgeID string
4951
portainerEdgeKey string
5052
portainerEdgeAsync bool
53+
portainerEdgeImage string
5154
loadBalancer bool
5255
localStorage bool
5356
localStorageSharedPath string
@@ -82,6 +85,11 @@ func service() (*kubesolo, error) {
8285
log.Fatal().Str("component", "kubesolo").Msg("--d2k requires --load-balancer: the d2k Service endpoint is populated by the LoadBalancer webhook")
8386
}
8487

88+
portainerEdgeImage, err := normaliseImageRef(*flags.PortainerEdgeImage)
89+
if err != nil {
90+
return nil, err
91+
}
92+
8593
return &kubesolo{
8694
hostName: system.GetHostname(),
8795
extraSANs: *flags.APIServerExtraSANs,
@@ -90,6 +98,7 @@ func service() (*kubesolo, error) {
9098
portainerEdgeID: *flags.PortainerEdgeID,
9199
portainerEdgeKey: *flags.PortainerEdgeKey,
92100
portainerEdgeAsync: *flags.PortainerEdgeAsync,
101+
portainerEdgeImage: portainerEdgeImage,
93102
loadBalancer: *flags.LoadBalancer,
94103
localStorage: *flags.LocalStorage,
95104
localStorageSharedPath: *flags.LocalStorageSharedPath,
@@ -102,6 +111,19 @@ func service() (*kubesolo, error) {
102111
}, nil
103112
}
104113

114+
// normaliseImageRef expands a short image reference such as portainerci/agent:develop
115+
// into a fully qualified one (docker.io/portainerci/agent:develop). The containerd
116+
// client, unlike the Docker CLI, applies no Docker Hub defaults and would otherwise
117+
// treat the first component as a registry host and fail to resolve it.
118+
func normaliseImageRef(image string) (string, error) {
119+
named, err := reference.ParseNormalizedNamed(image)
120+
if err != nil {
121+
return "", fmt.Errorf("invalid image reference %q: %v", image, err)
122+
}
123+
124+
return reference.TagNameOnly(named).String(), nil
125+
}
126+
105127
// main is the entry point for the kubesolo application
106128
// it parses the command line arguments and creates a new kubesolo application
107129
// it then bootstraps the application and runs it
@@ -289,6 +311,7 @@ func (s *kubesolo) run() {
289311
if s.portainerEdgeID != "" && s.portainerEdgeKey != "" {
290312
log.Info().Str("component", "kubesolo").Msg("deploying portainer edge agent...")
291313
if err := portainer.DeployEdgeAgent(s.embedded.AdminKubeconfigFile, types.EdgeAgentConfig{
314+
Image: s.portainerEdgeImage,
292315
EdgeID: s.portainerEdgeID,
293316
EdgeKey: s.portainerEdgeKey,
294317
EdgeAsync: s.portainerEdgeAsync,
@@ -579,7 +602,7 @@ func (s *kubesolo) bootstrap() {
579602
WebhookDir: filepath.Join(basePath, types.KubesoloWebhookDir),
580603

581604
// Image paths
582-
PortainerAgentImageFile: filepath.Join(basePath, types.DefaultContainerdDir, "images", "portainer-agent.tar.gz"),
605+
PortainerEdgeImageFile: filepath.Join(basePath, types.DefaultContainerdDir, "images", "portainer-agent.tar.gz"),
583606
CorednsImageFile: filepath.Join(basePath, types.DefaultContainerdDir, "images", "coredns.tar.gz"),
584607
SandboxImageFile: filepath.Join(basePath, types.DefaultContainerdDir, "images", "pause.tar.gz"),
585608
LocalPathProvisionerImageFile: filepath.Join(basePath, types.DefaultContainerdDir, "images", "local-path-provisioner.tar.gz"),
@@ -591,7 +614,8 @@ func (s *kubesolo) bootstrap() {
591614
LocalPathStorageDir: filepath.Join(basePath, types.DefaultLocalPathStorageDir),
592615

593616
// Portainer Edge
594-
IsPortainerEdge: s.portainerEdgeID != "" && s.portainerEdgeKey != "",
617+
IsPortainerEdge: s.portainerEdgeID != "" && s.portainerEdgeKey != "",
618+
PortainerEdgeImage: s.portainerEdgeImage,
595619

596620
// Container Mode
597621
ContainerMode: containerMode,

cmd/kubesolo/main_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package main
2+
3+
import "testing"
4+
5+
func TestNormaliseImageRef(t *testing.T) {
6+
tests := []struct {
7+
in string
8+
want string
9+
}{
10+
{"docker.io/portainer/agent:lts", "docker.io/portainer/agent:lts"},
11+
{"portainer/agent:lts", "docker.io/portainer/agent:lts"},
12+
{"portainerci/agent:develop", "docker.io/portainerci/agent:develop"},
13+
{"portainerci/agent", "docker.io/portainerci/agent:latest"},
14+
{"ghcr.io/portainer/agent:1.0", "ghcr.io/portainer/agent:1.0"},
15+
{"localhost:5000/agent:dev", "localhost:5000/agent:dev"},
16+
}
17+
18+
for _, tt := range tests {
19+
got, err := normaliseImageRef(tt.in)
20+
if err != nil {
21+
t.Errorf("normaliseImageRef(%q) returned error: %v", tt.in, err)
22+
continue
23+
}
24+
if got != tt.want {
25+
t.Errorf("normaliseImageRef(%q) = %q, want %q", tt.in, got, tt.want)
26+
}
27+
}
28+
29+
if _, err := normaliseImageRef("NOT A REF"); err == nil {
30+
t.Errorf("normaliseImageRef should reject an invalid reference")
31+
}
32+
}

docs/installation/flags.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,27 @@ curl -sfL https://get.kubesolo.io | \
116116

117117
---
118118

119+
### --portainer-edge-image
120+
121+
Full image reference deployed for the Portainer Edge Agent, including the tag. Accepts any registry, repository, and tag. Only the default image is loaded from the bundled image; any other reference is pulled from the registry, so the node needs access to it. If the registry cannot be reached at startup, KubeSolo logs a warning and continues — the kubelet retries the pull when the agent pod starts.
122+
123+
Short references are expanded the way Docker expands them, so `portainerci/agent:develop` becomes `docker.io/portainerci/agent:develop` and an omitted tag defaults to `latest`. Use a full host prefix for other registries, e.g. `ghcr.io/portainer/agent:2.34.0`.
124+
125+
Note that the deployment is only created once — on reboot it is restored from the database. Changing this flag on an existing installation does not update an already deployed agent.
126+
127+
| Flag | Env var | Default |
128+
|---|---|---|
129+
| `--portainer-edge-image=IMAGE` | `KUBESOLO_PORTAINER_EDGE_IMAGE` | `docker.io/portainer/agent:lts` |
130+
131+
```bash
132+
curl -sfL https://get.kubesolo.io | \
133+
KUBESOLO_PORTAINER_EDGE_ID=<your-edge-id> \
134+
KUBESOLO_PORTAINER_EDGE_KEY=<your-edge-key> \
135+
sudo -E sh -s -- --portainer-edge-image=docker.io/portainer/agent:sts
136+
```
137+
138+
---
139+
119140
### --local-storage
120141

121142
Enable the [Local Path Provisioner](https://github.com/rancher/local-path-provisioner), which creates a `local-path` StorageClass backed by host-local directories. Workloads that request persistent volumes will have them provisioned automatically under the KubeSolo data path.

docs/installation/kubesoloctl.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ Common flags:
104104
| `--container-ports` | `KUBESOLO_CONTAINER_PORTS` | _(none)_ | Workload host ports to publish (container mode) |
105105
| `--d2k` | `KUBESOLO_D2K` | `false` | Enable the Docker-compatible API translator |
106106
| `--local-storage` | `KUBESOLO_LOCAL_STORAGE` | `false` | Enable the local-path storage provisioner |
107+
| `--portainer-edge-image` | `KUBESOLO_PORTAINER_EDGE_IMAGE` | `docker.io/portainer/agent:lts` | Image deployed for the Portainer edge agent |
107108
| `--offline-install` | `KUBESOLO_OFFLINE_INSTALL` | _(none)_ | Install from a local tarball/binary instead of downloading |
108109
| `--proxy` | `KUBESOLO_PROXY` | _(none)_ | HTTP/HTTPS proxy injected into the service environment |
109110

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ require (
77
github.com/containerd/containerd/v2 v2.2.3
88
github.com/containerd/errdefs v1.0.0
99
github.com/containerd/fuse-overlayfs-snapshotter/v2 v2.1.7
10+
github.com/distribution/reference v0.6.0
1011
github.com/docker/docker v28.5.2+incompatible
1112
github.com/docker/go-connections v0.5.0
1213
github.com/k3s-io/kine v0.15.0
@@ -78,7 +79,6 @@ require (
7879
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
7980
github.com/cyphar/filepath-securejoin v0.6.0 // indirect
8081
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
81-
github.com/distribution/reference v0.6.0 // indirect
8282
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
8383
github.com/docker/go-metrics v0.0.1 // indirect
8484
github.com/docker/go-units v0.5.0 // indirect

install.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,6 +1082,7 @@ APISERVER_EXTRA_SANS="${KUBESOLO_APISERVER_EXTRA_SANS:-}"
10821082
PORTAINER_EDGE_ID="${KUBESOLO_PORTAINER_EDGE_ID:-}"
10831083
PORTAINER_EDGE_KEY="${KUBESOLO_PORTAINER_EDGE_KEY:-}"
10841084
PORTAINER_EDGE_ASYNC="${KUBESOLO_PORTAINER_EDGE_ASYNC:-false}"
1085+
PORTAINER_EDGE_IMAGE="${KUBESOLO_PORTAINER_EDGE_IMAGE:-}"
10851086
LOAD_BALANCER="${KUBESOLO_LOAD_BALANCER:-true}"
10861087
LOCAL_STORAGE="${KUBESOLO_LOCAL_STORAGE:-true}"
10871088
LOCAL_STORAGE_SHARED_PATH="${KUBESOLO_LOCAL_STORAGE_SHARED_PATH:-}"
@@ -1120,6 +1121,9 @@ for arg in "$@"; do
11201121
--portainer-edge-async=*)
11211122
PORTAINER_EDGE_ASYNC="${arg#*=}"
11221123
;;
1124+
--portainer-edge-image=*)
1125+
PORTAINER_EDGE_IMAGE="${arg#*=}"
1126+
;;
11231127
--local-storage=*)
11241128
LOCAL_STORAGE="${arg#*=}"
11251129
;;
@@ -1165,6 +1169,7 @@ for arg in "$@"; do
11651169
echo " --portainer-edge-id=ID Set Portainer Edge ID"
11661170
echo " --portainer-edge-key=KEY Set Portainer Edge Key"
11671171
echo " --portainer-edge-async=true|false Enable Portainer Edge Async (default: $PORTAINER_EDGE_ASYNC)"
1172+
echo " --portainer-edge-image=IMAGE Set the Portainer Edge Agent image (default: docker.io/portainer/agent:lts)"
11681173
echo " --local-storage=true|false Enable local storage (default: $LOCAL_STORAGE)"
11691174
echo " --d2k=true|false Embed d2k Docker-to-Kubernetes API translator (default: $D2K)"
11701175
echo " --d2k-namespace=NAMESPACE Namespace into which d2k is deployed (default: $D2K_NAMESPACE)"
@@ -1304,6 +1309,10 @@ if [ "$PORTAINER_EDGE_ASYNC" = "true" ]; then
13041309
CMD_ARGS="$CMD_ARGS --portainer-edge-async=true"
13051310
fi
13061311

1312+
if [ -n "$PORTAINER_EDGE_IMAGE" ]; then
1313+
CMD_ARGS="$CMD_ARGS --portainer-edge-image=$PORTAINER_EDGE_IMAGE"
1314+
fi
1315+
13071316
if [ "$LOAD_BALANCER" = "false" ]; then
13081317
CMD_ARGS="$CMD_ARGS --load-balancer=false"
13091318
fi

internal/cli/cmd_install.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ func addInstallFlags(cmd *cobra.Command, cfg *config.Config) {
6161
envBool("KUBESOLO_PORTAINER_EDGE_ASYNC", false),
6262
"Enable async mode for the Portainer edge agent")
6363

64+
f.StringVar(&cfg.PortainerEdgeImage, "portainer-edge-image",
65+
os.Getenv("KUBESOLO_PORTAINER_EDGE_IMAGE"),
66+
"Image deployed for the Portainer edge agent, including the tag\n"+
67+
"(default: docker.io/portainer/agent:lts). Any other image is pulled from the registry")
68+
6469
f.BoolVar(&cfg.LocalStorage, "local-storage",
6570
envBool("KUBESOLO_LOCAL_STORAGE", false),
6671
"Enable the local-path storage provisioner")

0 commit comments

Comments
 (0)