mirror of
https://github.com/siderolabs/talos.git
synced 2025-08-21 14:41:12 +02:00
This PR will introduce a `-p/--exposed-ports` flag to talosctl. This flag will allow us to enable port forwards on worker nodes only. This will allow for ingresses on docker clusters so we can hopefully use ingress for Arges initial bootstrapping. I modeled this after how KIND allows ingresses [here](https://kind.sigs.k8s.io/docs/user/ingress/) Signed-off-by: Spencer Smith <robertspencersmith@gmail.com>
242 lines
6.3 KiB
Go
242 lines
6.3 KiB
Go
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
package docker
|
|
|
|
import (
|
|
"context"
|
|
"encoding/base64"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"strings"
|
|
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/docker/docker/api/types/container"
|
|
"github.com/docker/docker/api/types/filters"
|
|
"github.com/docker/docker/api/types/network"
|
|
"github.com/docker/go-connections/nat"
|
|
"github.com/hashicorp/go-multierror"
|
|
|
|
"github.com/talos-systems/talos/internal/pkg/provision"
|
|
"github.com/talos-systems/talos/pkg/config/machine"
|
|
"github.com/talos-systems/talos/pkg/constants"
|
|
)
|
|
|
|
type portMap struct {
|
|
exposedPorts nat.PortSet
|
|
portBindings nat.PortMap
|
|
}
|
|
|
|
func (p *provisioner) createNodes(ctx context.Context, clusterReq provision.ClusterRequest, nodeReqs []provision.NodeRequest, options *provision.Options) ([]provision.NodeInfo, error) {
|
|
errCh := make(chan error)
|
|
nodeCh := make(chan provision.NodeInfo, len(nodeReqs))
|
|
|
|
for _, nodeReq := range nodeReqs {
|
|
go func(nodeReq provision.NodeRequest) {
|
|
nodeInfo, err := p.createNode(ctx, clusterReq, nodeReq, options)
|
|
errCh <- err
|
|
|
|
if err == nil {
|
|
nodeCh <- nodeInfo
|
|
}
|
|
}(nodeReq)
|
|
}
|
|
|
|
var multiErr *multierror.Error
|
|
|
|
for range nodeReqs {
|
|
multiErr = multierror.Append(multiErr, <-errCh)
|
|
}
|
|
|
|
close(nodeCh)
|
|
|
|
nodesInfo := make([]provision.NodeInfo, 0, len(nodeReqs))
|
|
|
|
for nodeInfo := range nodeCh {
|
|
nodesInfo = append(nodesInfo, nodeInfo)
|
|
}
|
|
|
|
return nodesInfo, multiErr.ErrorOrNil()
|
|
}
|
|
|
|
//nolint: gocyclo
|
|
func (p *provisioner) createNode(ctx context.Context, clusterReq provision.ClusterRequest, nodeReq provision.NodeRequest, options *provision.Options) (provision.NodeInfo, error) {
|
|
cfg, err := nodeReq.Config.String()
|
|
if err != nil {
|
|
return provision.NodeInfo{}, err
|
|
}
|
|
|
|
// Create the container config.
|
|
containerConfig := &container.Config{
|
|
Hostname: nodeReq.Name,
|
|
Image: clusterReq.Image,
|
|
Env: []string{"PLATFORM=container", "USERDATA=" + base64.StdEncoding.EncodeToString([]byte(cfg))},
|
|
Labels: map[string]string{
|
|
"talos.owned": "true",
|
|
"talos.cluster.name": clusterReq.Name,
|
|
"talos.type": nodeReq.Config.Machine().Type().String(),
|
|
},
|
|
Volumes: map[string]struct{}{
|
|
"/var/lib/containerd": {},
|
|
"/var/lib/kubelet": {},
|
|
"/etc/cni": {},
|
|
"/run": {},
|
|
},
|
|
}
|
|
|
|
// Create the host config.
|
|
|
|
hostConfig := &container.HostConfig{
|
|
Privileged: true,
|
|
SecurityOpt: []string{"seccomp:unconfined"},
|
|
Resources: container.Resources{
|
|
NanoCPUs: nodeReq.NanoCPUs,
|
|
Memory: nodeReq.Memory,
|
|
},
|
|
}
|
|
|
|
// Ensure that the container is created in the talos network.
|
|
|
|
networkConfig := &network.NetworkingConfig{
|
|
EndpointsConfig: map[string]*network.EndpointSettings{
|
|
clusterReq.Network.Name: {
|
|
NetworkID: clusterReq.Network.Name,
|
|
},
|
|
},
|
|
}
|
|
|
|
// Mutate the container configurations based on the node type.
|
|
|
|
switch nodeReq.Config.Machine().Type() {
|
|
case machine.TypeInit:
|
|
portsToOpen := []string{"50000:50000/tcp", "6443:6443/tcp"}
|
|
|
|
if len(options.DockerPorts) > 0 {
|
|
portsToOpen = append(portsToOpen, options.DockerPorts...)
|
|
}
|
|
|
|
var generatedPortMap portMap
|
|
|
|
generatedPortMap, err = genPortMap(portsToOpen)
|
|
if err != nil {
|
|
return provision.NodeInfo{}, err
|
|
}
|
|
|
|
containerConfig.ExposedPorts = generatedPortMap.exposedPorts
|
|
|
|
hostConfig.PortBindings = generatedPortMap.portBindings
|
|
|
|
fallthrough
|
|
case machine.TypeControlPlane:
|
|
containerConfig.Volumes[constants.EtcdDataPath] = struct{}{}
|
|
|
|
if nodeReq.IP == nil {
|
|
return provision.NodeInfo{}, errors.New("an IP address must be provided when creating a master node")
|
|
}
|
|
}
|
|
|
|
if nodeReq.IP != nil {
|
|
networkConfig.EndpointsConfig[clusterReq.Network.Name].IPAMConfig = &network.EndpointIPAMConfig{IPv4Address: nodeReq.IP.String()}
|
|
}
|
|
|
|
// Create the container.
|
|
resp, err := p.client.ContainerCreate(ctx, containerConfig, hostConfig, networkConfig, nodeReq.Name)
|
|
if err != nil {
|
|
return provision.NodeInfo{}, err
|
|
}
|
|
|
|
// Start the container.
|
|
err = p.client.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{})
|
|
if err != nil {
|
|
return provision.NodeInfo{}, err
|
|
}
|
|
|
|
// Inspect the container.
|
|
info, err := p.client.ContainerInspect(ctx, resp.ID)
|
|
if err != nil {
|
|
return provision.NodeInfo{}, err
|
|
}
|
|
|
|
nodeInfo := provision.NodeInfo{
|
|
ID: info.ID,
|
|
Name: info.Name,
|
|
Type: nodeReq.Config.Machine().Type(),
|
|
|
|
NanoCPUs: nodeReq.NanoCPUs,
|
|
Memory: nodeReq.Memory,
|
|
|
|
PrivateIP: net.ParseIP(info.NetworkSettings.Networks[clusterReq.Network.Name].IPAddress),
|
|
}
|
|
|
|
return nodeInfo, nil
|
|
}
|
|
|
|
func (p *provisioner) listNodes(ctx context.Context, clusterName string) ([]types.Container, error) {
|
|
filters := filters.NewArgs()
|
|
filters.Add("label", "talos.owned=true")
|
|
filters.Add("label", "talos.cluster.name="+clusterName)
|
|
|
|
return p.client.ContainerList(ctx, types.ContainerListOptions{All: true, Filters: filters})
|
|
}
|
|
|
|
func (p *provisioner) destroyNodes(ctx context.Context, clusterName string, options *provision.Options) error {
|
|
containers, err := p.listNodes(ctx, clusterName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
errCh := make(chan error)
|
|
|
|
for _, container := range containers {
|
|
go func(container types.Container) {
|
|
fmt.Fprintln(options.LogWriter, "destroying node", container.Names[0][1:])
|
|
|
|
errCh <- p.client.ContainerRemove(ctx, container.ID, types.ContainerRemoveOptions{RemoveVolumes: true, Force: true})
|
|
}(container)
|
|
}
|
|
|
|
var multiErr *multierror.Error
|
|
|
|
for range containers {
|
|
multiErr = multierror.Append(multiErr, <-errCh)
|
|
}
|
|
|
|
return multiErr.ErrorOrNil()
|
|
}
|
|
|
|
func genPortMap(portList []string) (portMap, error) {
|
|
portSetRet := nat.PortSet{}
|
|
portMapRet := nat.PortMap{}
|
|
|
|
for _, port := range portList {
|
|
explodedPortAndProtocol := strings.Split(port, "/")
|
|
|
|
if len(explodedPortAndProtocol) != 2 {
|
|
return portMap{}, errors.New("incorrect format for exposed port/protocols")
|
|
}
|
|
|
|
explodedPort := strings.Split(explodedPortAndProtocol[0], ":")
|
|
|
|
if len(explodedPort) != 2 {
|
|
return portMap{}, errors.New("incorrect format for exposed ports")
|
|
}
|
|
|
|
natPort, err := nat.NewPort(explodedPortAndProtocol[1], explodedPort[1])
|
|
if err != nil {
|
|
return portMap{}, err
|
|
}
|
|
|
|
portSetRet[natPort] = struct{}{}
|
|
portMapRet[natPort] = []nat.PortBinding{
|
|
{
|
|
HostIP: "0.0.0.0",
|
|
HostPort: explodedPort[0],
|
|
},
|
|
}
|
|
}
|
|
|
|
return portMap{portSetRet, portMapRet}, nil
|
|
}
|