Skip to content

Commit deb01c3

Browse files
Copilotdevantler
andcommitted
fix: use Docker container for cloud-provider-kind instead of Helm
Cloud-provider-kind is not distributed as a Helm chart. The correct approach is to run it as a Docker container with access to the Docker socket, connected to the KIND network. This commit: - Rewrites the cloud-provider-kind installer to use Docker API instead of Helm - Creates and manages the cloud-provider-kind container with proper configuration (Docker socket mount, KIND network connection) - Updates tests to use Docker client mocks instead of Helm mocks - Uses the official image: registry.k8s.io/cloud-provider-kind/cloud-controller-manager:latest Fixes the 404 error when trying to fetch non-existent Helm chart from https://kubernetes-sigs.github.io/cloud-provider-kind/index.yaml Co-authored-by: devantler <[email protected]>
1 parent 26e4b8c commit deb01c3

File tree

3 files changed

+371
-160
lines changed

3 files changed

+371
-160
lines changed

pkg/cli/setup/components.go

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -299,28 +299,24 @@ func InstallMetricsServerSilent(
299299
}
300300

301301
// InstallLoadBalancerSilent installs LoadBalancer support silently for parallel execution.
302-
// For Vanilla (Kind) × Docker, installs Cloud Provider KIND.
302+
// For Vanilla (Kind) × Docker, installs Cloud Provider KIND as a Docker container.
303303
func InstallLoadBalancerSilent(
304304
ctx context.Context,
305305
clusterCfg *v1alpha1.Cluster,
306306
factories *InstallerFactories,
307307
) error {
308-
helmClient, kubeconfig, timeout, err := helmClientSetup(clusterCfg, factories)
309-
if err != nil {
310-
return err
311-
}
312-
313308
// Determine which LoadBalancer implementation to install based on distribution × provider
314309
switch clusterCfg.Spec.Cluster.Distribution {
315310
case v1alpha1.DistributionVanilla:
316311
// Vanilla (Kind) × Docker uses Cloud Provider KIND
317312
if clusterCfg.Spec.Cluster.Provider == v1alpha1.ProviderDocker {
318-
lbInstaller := cloudproviderkindinstaller.NewCloudProviderKINDInstaller(
319-
helmClient,
320-
kubeconfig,
321-
clusterCfg.Spec.Cluster.Connection.Context,
322-
timeout,
323-
)
313+
// Create Docker client for container management
314+
dockerClient, err := dockerclient.GetDockerClient()
315+
if err != nil {
316+
return fmt.Errorf("failed to create Docker client: %w", err)
317+
}
318+
319+
lbInstaller := cloudproviderkindinstaller.NewCloudProviderKINDInstaller(dockerClient)
324320

325321
installErr := lbInstaller.Install(ctx)
326322
if installErr != nil {

pkg/svc/installer/cloudproviderkind/installer.go

Lines changed: 161 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -3,82 +3,203 @@ package cloudproviderkindinstaller
33
import (
44
"context"
55
"fmt"
6-
"time"
6+
"io"
7+
"strings"
78

8-
"github.com/devantler-tech/ksail/v5/pkg/client/helm"
9+
"github.com/docker/docker/api/types/container"
10+
"github.com/docker/docker/api/types/filters"
11+
"github.com/docker/docker/api/types/image"
12+
"github.com/docker/docker/api/types/mount"
13+
"github.com/docker/docker/api/types/network"
14+
"github.com/docker/docker/client"
915
)
1016

11-
// CloudProviderKINDInstaller installs or upgrades Cloud Provider KIND.
17+
const (
18+
// CloudProviderKindImage is the official cloud-provider-kind image.
19+
CloudProviderKindImage = "registry.k8s.io/cloud-provider-kind/cloud-controller-manager:latest"
20+
// CloudProviderKindContainerName is the name of the cloud-provider-kind container.
21+
CloudProviderKindContainerName = "cloud-provider-kind"
22+
// CloudProviderKindNetworkName is the default KIND network name.
23+
CloudProviderKindNetworkName = "kind"
24+
// DockerSocketPath is the path to the Docker socket.
25+
DockerSocketPath = "/var/run/docker.sock"
26+
)
27+
28+
// CloudProviderKINDInstaller manages the cloud-provider-kind Docker container.
1229
type CloudProviderKINDInstaller struct {
13-
kubeconfig string
14-
context string
15-
timeout time.Duration
16-
client helm.Interface
30+
client client.APIClient
1731
}
1832

1933
// NewCloudProviderKINDInstaller creates a new Cloud Provider KIND installer instance.
2034
func NewCloudProviderKINDInstaller(
21-
client helm.Interface,
22-
kubeconfig, context string,
23-
timeout time.Duration,
35+
dockerClient client.APIClient,
2436
) *CloudProviderKINDInstaller {
2537
return &CloudProviderKINDInstaller{
26-
client: client,
27-
kubeconfig: kubeconfig,
28-
context: context,
29-
timeout: timeout,
38+
client: dockerClient,
3039
}
3140
}
3241

33-
// Install installs or upgrades Cloud Provider KIND via its Helm chart.
42+
// Install starts the cloud-provider-kind container if not already running.
3443
func (c *CloudProviderKINDInstaller) Install(ctx context.Context) error {
35-
err := c.helmInstallOrUpgradeCloudProviderKIND(ctx)
44+
// Check if container is already running
45+
running, err := c.isContainerRunning(ctx)
3646
if err != nil {
37-
return fmt.Errorf("failed to install cloud-provider-kind: %w", err)
47+
return fmt.Errorf("failed to check if container is running: %w", err)
48+
}
49+
50+
if running {
51+
return nil // Already running
52+
}
53+
54+
// Ensure the cloud-provider-kind image is available
55+
err = c.ensureImage(ctx)
56+
if err != nil {
57+
return fmt.Errorf("failed to ensure cloud-provider-kind image: %w", err)
58+
}
59+
60+
// Create and start the container
61+
err = c.createAndStartContainer(ctx)
62+
if err != nil {
63+
return fmt.Errorf("failed to create and start cloud-provider-kind container: %w", err)
3864
}
3965

4066
return nil
4167
}
4268

43-
// Uninstall uninstalls Cloud Provider KIND via Helm.
69+
// Uninstall stops and removes the cloud-provider-kind container.
4470
func (c *CloudProviderKINDInstaller) Uninstall(ctx context.Context) error {
45-
err := c.client.UninstallRelease(ctx, "cloud-provider-kind", "kube-system")
71+
containers, err := c.listContainers(ctx)
4672
if err != nil {
47-
return fmt.Errorf("failed to uninstall cloud-provider-kind: %w", err)
73+
return fmt.Errorf("failed to list containers: %w", err)
74+
}
75+
76+
if len(containers) == 0 {
77+
return nil // Nothing to uninstall
78+
}
79+
80+
// Stop and remove container
81+
for _, ctr := range containers {
82+
// Stop container if running
83+
if strings.EqualFold(ctr.State, "running") {
84+
err = c.client.ContainerStop(ctx, ctr.ID, container.StopOptions{})
85+
if err != nil {
86+
return fmt.Errorf("failed to stop container: %w", err)
87+
}
88+
}
89+
90+
// Remove container
91+
err = c.client.ContainerRemove(ctx, ctr.ID, container.RemoveOptions{
92+
Force: true,
93+
})
94+
if err != nil {
95+
return fmt.Errorf("failed to remove container: %w", err)
96+
}
4897
}
4998

5099
return nil
51100
}
52101

53102
// --- internals ---
54103

55-
func (c *CloudProviderKINDInstaller) helmInstallOrUpgradeCloudProviderKIND(
56-
ctx context.Context,
57-
) error {
58-
repoEntry := &helm.RepositoryEntry{
59-
Name: "cloud-provider-kind",
60-
URL: "https://kubernetes-sigs.github.io/cloud-provider-kind",
104+
// isContainerRunning checks if the cloud-provider-kind container is running.
105+
func (c *CloudProviderKINDInstaller) isContainerRunning(ctx context.Context) (bool, error) {
106+
containers, err := c.listContainers(ctx)
107+
if err != nil {
108+
return false, err
109+
}
110+
111+
if len(containers) == 0 {
112+
return false, nil
113+
}
114+
115+
return strings.EqualFold(containers[0].State, "running"), nil
116+
}
117+
118+
// listContainers lists all cloud-provider-kind containers.
119+
func (c *CloudProviderKINDInstaller) listContainers(ctx context.Context) ([]container.Summary, error) {
120+
filterArgs := filters.NewArgs()
121+
filterArgs.Add("name", "^/"+CloudProviderKindContainerName+"$")
122+
123+
containers, err := c.client.ContainerList(ctx, container.ListOptions{
124+
All: true,
125+
Filters: filterArgs,
126+
})
127+
if err != nil {
128+
return nil, fmt.Errorf("failed to list containers: %w", err)
61129
}
62130

63-
addRepoErr := c.client.AddRepository(ctx, repoEntry, c.timeout)
64-
if addRepoErr != nil {
65-
return fmt.Errorf("failed to add cloud-provider-kind repository: %w", addRepoErr)
131+
return containers, nil
132+
}
133+
134+
// ensureImage pulls the cloud-provider-kind image if not already present.
135+
func (c *CloudProviderKINDInstaller) ensureImage(ctx context.Context) error {
136+
// Check if image exists
137+
_, err := c.client.ImageInspect(ctx, CloudProviderKindImage)
138+
if err == nil {
139+
return nil // Image already exists
140+
}
141+
142+
// Pull image
143+
reader, err := c.client.ImagePull(ctx, CloudProviderKindImage, image.PullOptions{})
144+
if err != nil {
145+
return fmt.Errorf("failed to pull image: %w", err)
146+
}
147+
defer reader.Close()
148+
149+
// Consume pull output
150+
_, err = io.Copy(io.Discard, reader)
151+
if err != nil {
152+
return fmt.Errorf("failed to read image pull output: %w", err)
66153
}
67154

68-
spec := &helm.ChartSpec{
69-
ReleaseName: "cloud-provider-kind",
70-
ChartName: "cloud-provider-kind/cloud-provider-kind",
71-
Namespace: "kube-system",
72-
RepoURL: "https://kubernetes-sigs.github.io/cloud-provider-kind",
73-
Atomic: true,
74-
Wait: true,
75-
WaitForJobs: true,
76-
Timeout: c.timeout,
155+
return nil
156+
}
157+
158+
// createAndStartContainer creates and starts the cloud-provider-kind container.
159+
func (c *CloudProviderKINDInstaller) createAndStartContainer(ctx context.Context) error {
160+
// Container configuration
161+
containerConfig := &container.Config{
162+
Image: CloudProviderKindImage,
163+
}
164+
165+
// Host configuration - mount Docker socket
166+
hostConfig := &container.HostConfig{
167+
Mounts: []mount.Mount{
168+
{
169+
Type: mount.TypeBind,
170+
Source: DockerSocketPath,
171+
Target: DockerSocketPath,
172+
},
173+
},
174+
RestartPolicy: container.RestartPolicy{
175+
Name: "unless-stopped",
176+
},
177+
}
178+
179+
// Network configuration - connect to KIND network
180+
networkConfig := &network.NetworkingConfig{
181+
EndpointsConfig: map[string]*network.EndpointSettings{
182+
CloudProviderKindNetworkName: {},
183+
},
184+
}
185+
186+
// Create container
187+
resp, err := c.client.ContainerCreate(
188+
ctx,
189+
containerConfig,
190+
hostConfig,
191+
networkConfig,
192+
nil,
193+
CloudProviderKindContainerName,
194+
)
195+
if err != nil {
196+
return fmt.Errorf("failed to create container: %w", err)
77197
}
78198

79-
_, err := c.client.InstallOrUpgradeChart(ctx, spec)
199+
// Start container
200+
err = c.client.ContainerStart(ctx, resp.ID, container.StartOptions{})
80201
if err != nil {
81-
return fmt.Errorf("failed to install cloud-provider-kind chart: %w", err)
202+
return fmt.Errorf("failed to start container: %w", err)
82203
}
83204

84205
return nil

0 commit comments

Comments
 (0)