Skip to content

Commit bbe00a1

Browse files
orangedengJacieChao
authored andcommitted
feat(k3d): Auto detect ip when setting 0.0.0.0 for API Port field
When running autok3s in docker, 0.0.0.0 won't work as bridge network is used. Now the autok3s will detect docker host IP and set it to tls-san for k3d cluster Also when creating cluster via UI, the request hostname will also added to tls-san when running inside container.
1 parent 7d9afa5 commit bbe00a1

File tree

4 files changed

+178
-13
lines changed

4 files changed

+178
-13
lines changed

pkg/providers/k3d/docker.go

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package k3d
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net"
7+
"net/url"
8+
"os"
9+
"strings"
10+
"sync"
11+
12+
"github.com/docker/docker/api/types"
13+
"github.com/docker/docker/api/types/container"
14+
"github.com/docker/docker/api/types/filters"
15+
"github.com/k3d-io/k3d/v5/pkg/runtimes"
16+
"github.com/k3d-io/k3d/v5/pkg/runtimes/docker"
17+
"github.com/sirupsen/logrus"
18+
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
19+
)
20+
21+
var (
22+
dockerHost string
23+
once sync.Once
24+
)
25+
26+
func getDockerHost() string {
27+
once.Do(func() {
28+
var err error
29+
if runtimes.SelectedRuntime.ID() != "docker" {
30+
logrus.Debugf("runtime not docker")
31+
return
32+
}
33+
// TODO find a better way to get container id via docker.sock
34+
// Hostname as container id is not 100% reliable.
35+
hostname := os.Getenv("HOSTNAME_OVERRIDE")
36+
if hostname == "" {
37+
hostname, err = os.Hostname()
38+
if err != nil {
39+
logrus.Debugf("failed to get hostname, %v", err)
40+
return
41+
}
42+
}
43+
44+
runtime := runtimes.Docker
45+
46+
if runtime.GetHost() != "" {
47+
logrus.Debugf("runtime host is %s", runtime.GetHost())
48+
return
49+
}
50+
51+
if _, err = os.Stat(runtime.GetRuntimePath()); err != nil {
52+
logrus.Debugf("runtime path %s doesn't exist", runtime.GetRuntimePath())
53+
return
54+
}
55+
nodes, err := getContainersByLabel(context.Background(), map[string]string{
56+
"org.opencontainers.image.title": "autok3s",
57+
})
58+
if err != nil {
59+
logrus.Debugf("failed to get container from runtime %s, %v", runtime.ID(), err)
60+
return
61+
}
62+
if len(nodes) == 0 {
63+
logrus.Debug("autok3s docker container not found. Skip finding docker host IP.")
64+
return
65+
}
66+
var currentContainer *types.Container
67+
for i := range nodes {
68+
node := nodes[i]
69+
if strings.HasPrefix(node.ID, hostname) {
70+
currentContainer = &node
71+
break
72+
}
73+
}
74+
if currentContainer == nil {
75+
logrus.Debugf("no container found for hostname %s", hostname)
76+
return
77+
}
78+
if currentContainer.HostConfig.NetworkMode == "host" {
79+
logrus.Debug("do nothing when running host network")
80+
return
81+
}
82+
logrus.Debugf("found container %s", currentContainer.ID)
83+
gw, err := runtime.GetHostIP(context.Background(), currentContainer.HostConfig.NetworkMode)
84+
if err != nil {
85+
logrus.Debugf("failed to get gateway ip for network %s, %v", currentContainer.HostConfig.NetworkMode, err)
86+
return
87+
}
88+
dockerHost = gw.String()
89+
logrus.Infof("found docker host IP %s", dockerHost)
90+
})
91+
return dockerHost
92+
}
93+
94+
func getContainersByLabel(ctx context.Context, labels map[string]string) ([]types.Container, error) {
95+
// (0) create docker client
96+
docker, err := docker.GetDockerClient()
97+
if err != nil {
98+
return nil, fmt.Errorf("Failed to create docker client. %+v", err)
99+
}
100+
defer docker.Close()
101+
102+
filters := filters.NewArgs()
103+
for k, v := range labels {
104+
filters.Add("label", fmt.Sprintf("%s=%s", k, v))
105+
}
106+
107+
containers, err := docker.ContainerList(ctx, container.ListOptions{
108+
Filters: filters,
109+
All: true,
110+
})
111+
if err != nil {
112+
return nil, fmt.Errorf("failed to list containers: %w", err)
113+
}
114+
115+
return containers, nil
116+
}
117+
118+
func OverrideK3dKubeConfigServer(from, to string, config *clientcmdapi.Config) {
119+
if to == "" {
120+
return
121+
}
122+
if from == "" && getDockerHost() != "" {
123+
from = getDockerHost()
124+
}
125+
for key := range config.Clusters {
126+
cluster := config.Clusters[key]
127+
serverURL, _ := url.Parse(cluster.Server)
128+
if serverURL.Hostname() == from {
129+
_, port, _ := net.SplitHostPort(serverURL.Host)
130+
serverURL.Host = fmt.Sprintf("%s:%s", to, port)
131+
}
132+
cluster.Server = serverURL.String()
133+
return
134+
}
135+
}

pkg/providers/k3d/k3d.go

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ var (
4545
type K3d struct {
4646
*cluster.ProviderBase `json:",inline"`
4747
typesk3d.Options `json:",inline"`
48+
49+
dockerHost, additionalHost string
4850
}
4951

5052
func init() {
@@ -62,6 +64,7 @@ func newProvider() *K3d {
6264
APIPort: k3dAPIPort,
6365
Image: k3dImage,
6466
},
67+
dockerHost: getDockerHost(),
6568
}
6669
}
6770

@@ -173,6 +176,11 @@ func (p *K3d) GetProviderOptions(opt []byte) (interface{}, error) {
173176

174177
// SetConfig set cluster config.
175178
func (p *K3d) SetConfig(config []byte) error {
179+
tmpHost := struct {
180+
AdditionalHost string `json:"additionalHost,omitempty"`
181+
}{}
182+
_ = json.Unmarshal(config, &tmpHost)
183+
176184
c, err := p.SetClusterConfig(config)
177185
if err != nil {
178186
return err
@@ -189,7 +197,7 @@ func (p *K3d) SetConfig(config []byte) error {
189197
}
190198
targetOption := reflect.ValueOf(opt).Elem()
191199
utils.MergeConfig(sourceOption, targetOption)
192-
200+
p.additionalHost = tmpHost.AdditionalHost
193201
return nil
194202
}
195203

@@ -326,6 +334,8 @@ func (p *K3d) obtainKubeCfg() (kubeCfg, ip string, err error) {
326334
return
327335
}
328336

337+
OverrideK3dKubeConfigServer(k3d.DefaultAPIHost, p.dockerHost, kubeConfig)
338+
329339
bytes, err := clientcmd.Write(*kubeConfig)
330340
if err != nil {
331341
return
@@ -611,20 +621,15 @@ func (p *K3d) wrapCliFlags(masters, workers int) (*k3dconf.ClusterConfig, error)
611621
}
612622

613623
if p.APIPort != "" {
614-
exposeAPI, err := k3dutil.ParsePortExposureSpec(p.APIPort, k3d.DefaultAPIPort)
624+
apiPort := p.APIPort
625+
if strings.HasSuffix(apiPort, ":0") {
626+
apiPort = strings.TrimSuffix(apiPort, ":0") + ":random"
627+
}
628+
exposeAPI, err := k3dutil.ParsePortExposureSpec(apiPort, k3d.DefaultAPIPort)
615629
if err != nil {
616630
return nil, fmt.Errorf("[%s] cluster %s parse port config failed: %w", p.GetProviderName(), p.Name, err)
617631
}
618-
619632
cfg.ExposeAPI.HostIP = exposeAPI.Binding.HostIP
620-
621-
if exposeAPI.Binding.HostPort == "0" {
622-
exposeAPI, err = k3dutil.ParsePortExposureSpec("random", k3d.DefaultAPIPort)
623-
if err != nil {
624-
return nil, fmt.Errorf("[%s] cluster %s parse random port config failed: %w", p.GetProviderName(), p.Name, err)
625-
}
626-
}
627-
628633
cfg.ExposeAPI.HostPort = exposeAPI.Binding.HostPort
629634
p.APIPort = fmt.Sprintf("%s:%s", cfg.ExposeAPI.HostIP, cfg.ExposeAPI.HostPort)
630635
}
@@ -645,6 +650,13 @@ func (p *K3d) wrapCliFlags(masters, workers int) (*k3dconf.ClusterConfig, error)
645650
cfg.Options.Runtime.AgentsMemory = p.WorkersMemory
646651
}
647652

653+
for _, host := range []string{p.additionalHost, p.dockerHost} {
654+
if host == "" {
655+
continue
656+
}
657+
p.MasterExtraArgs = strings.TrimPrefix(p.MasterExtraArgs+" --tls-san="+host, " ")
658+
}
659+
648660
if p.MasterExtraArgs != "" {
649661
cfg.Options.K3sOptions.ExtraArgs = []k3dconf.K3sArgWithNodeFilters{}
650662
for _, arg := range strings.Split(p.MasterExtraArgs, " ") {

pkg/server/store/cluster/action.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,20 @@ import (
55
"encoding/json"
66
"fmt"
77
"io"
8+
"net"
89
"net/http"
910
"path/filepath"
11+
"strings"
1012

1113
"github.com/cnrancher/autok3s/pkg/common"
1214
"github.com/cnrancher/autok3s/pkg/providers"
15+
"github.com/cnrancher/autok3s/pkg/providers/k3d"
1316
autok3stypes "github.com/cnrancher/autok3s/pkg/types/apis"
1417

1518
"github.com/gorilla/mux"
1619
"github.com/rancher/apiserver/pkg/apierror"
1720
"github.com/rancher/apiserver/pkg/types"
21+
"github.com/rancher/apiserver/pkg/urlbuilder"
1822
"github.com/rancher/wrangler/v2/pkg/schemas/validation"
1923
"github.com/sirupsen/logrus"
2024
"k8s.io/apimachinery/pkg/runtime"
@@ -256,6 +260,14 @@ func (d downloadKubeconfig) ServeHTTP(_ http.ResponseWriter, req *http.Request)
256260
}
257261
}
258262

263+
if strings.HasPrefix(clusterID, "k3d-") {
264+
host, _, _ := net.SplitHostPort(urlbuilder.GetHost(req, ""))
265+
// When parsing empty string as from parameter, the dockerHost will be used as the origin server
266+
// if the dockerHost is empty(e.g. DOCKER_HOST is set), this function will do nothing as the k3d already use the
267+
// proper cluster server address in kubeconfig
268+
k3d.OverrideK3dKubeConfigServer("", host, &currentCfg)
269+
}
270+
259271
result, err := clientcmd.Write(currentCfg)
260272
if err != nil {
261273
apiRequest.WriteError(apierror.NewAPIError(validation.ServerError, err.Error()))

pkg/server/store/cluster/store.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package cluster
33
import (
44
"encoding/json"
55
"fmt"
6+
"net"
67
"strings"
78

89
"github.com/cnrancher/autok3s/pkg/cluster"
@@ -14,6 +15,7 @@ import (
1415
"github.com/rancher/apiserver/pkg/apierror"
1516
"github.com/rancher/apiserver/pkg/store/empty"
1617
"github.com/rancher/apiserver/pkg/types"
18+
"github.com/rancher/apiserver/pkg/urlbuilder"
1719
"github.com/rancher/wrangler/v2/pkg/schemas/validation"
1820
"github.com/sirupsen/logrus"
1921
)
@@ -24,9 +26,13 @@ type Store struct {
2426
}
2527

2628
// Create creates cluster based on the request data.
27-
func (c *Store) Create(_ *types.APIRequest, schema *types.APISchema, data types.APIObject) (types.APIObject, error) {
29+
func (c *Store) Create(req *types.APIRequest, schema *types.APISchema, data types.APIObject) (types.APIObject, error) {
2830
providerName := data.Data().String("provider")
29-
b, err := json.Marshal(data.Data())
31+
// for k3d to add the request host for additional tls-san
32+
objMap := data.Data()
33+
host, _, _ := net.SplitHostPort(urlbuilder.GetHost(req.Request, ""))
34+
objMap.Set("additionalHost", host)
35+
b, err := json.Marshal(objMap)
3036
if err != nil {
3137
return types.APIObject{}, err
3238
}

0 commit comments

Comments
 (0)