Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for ulimits #1264

Merged
merged 5 commits into from
Apr 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion cmd/cluster/clusterCreate.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,12 @@ import (
"inet.af/netaddr"
"sigs.k8s.io/yaml"

"github.com/k3d-io/k3d/v5/cmd/util"
cliutil "github.com/k3d-io/k3d/v5/cmd/util"
cliconfig "github.com/k3d-io/k3d/v5/cmd/util/config"
k3dCluster "github.com/k3d-io/k3d/v5/pkg/client"
"github.com/k3d-io/k3d/v5/pkg/config"
conf "github.com/k3d-io/k3d/v5/pkg/config/v1alpha4"
conf "github.com/k3d-io/k3d/v5/pkg/config/v1alpha5"
l "github.com/k3d-io/k3d/v5/pkg/logger"
"github.com/k3d-io/k3d/v5/pkg/runtimes"
k3d "github.com/k3d-io/k3d/v5/pkg/types"
Expand Down Expand Up @@ -251,6 +252,9 @@ func NewCmdClusterCreate() *cobra.Command {
cmd.Flags().StringArrayP("runtime-label", "", nil, "Add label to container runtime (Format: `KEY[=VALUE][@NODEFILTER[;NODEFILTER...]]`\n - Example: `k3d cluster create --agents 2 --runtime-label \"my.label@agent:0,1\" --runtime-label \"other.label=somevalue@server:0\"`")
_ = ppViper.BindPFlag("cli.runtime-labels", cmd.Flags().Lookup("runtime-label"))

cmd.Flags().StringArrayP("runtime-ulimit", "", nil, "Add ulimit to container runtime (Format: `NAME[=SOFT]:[HARD]`\n - Example: `k3d cluster create --agents 2 --runtime-ulimit \"nofile=1024:1024\" --runtime-ulimit \"noproc=1024:1024\"`")
_ = ppViper.BindPFlag("cli.runtime-ulimits", cmd.Flags().Lookup("runtime-ulimit"))

cmd.Flags().String("registry-create", "", "Create a k3d-managed registry and connect it to the cluster (Format: `NAME[:HOST][:HOSTPORT]`\n - Example: `k3d cluster create --registry-create mycluster-registry:0.0.0.0:5432`")
_ = ppViper.BindPFlag("cli.registries.create", cmd.Flags().Lookup("registry-create"))

Expand Down Expand Up @@ -515,6 +519,10 @@ func applyCLIOverrides(cfg conf.SimpleConfig) (conf.SimpleConfig, error) {

l.Log().Tracef("RuntimeLabelFilterMap: %+v", runtimeLabelFilterMap)

for _, ulimit := range ppViper.GetStringSlice("cli.runtime-ulimits") {
cfg.Options.Runtime.Ulimits = append(cfg.Options.Runtime.Ulimits, *util.ParseRuntimeUlimit[conf.Ulimit](ulimit))
}

// --env
// envFilterMap will add container env vars to applied node filters
envFilterMap := make(map[string][]string, 1)
Expand Down
2 changes: 1 addition & 1 deletion cmd/cluster/clusterEdit.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import (
"github.com/k3d-io/k3d/v5/cmd/util"
cliutil "github.com/k3d-io/k3d/v5/cmd/util"
"github.com/k3d-io/k3d/v5/pkg/client"
conf "github.com/k3d-io/k3d/v5/pkg/config/v1alpha4"
conf "github.com/k3d-io/k3d/v5/pkg/config/v1alpha5"
l "github.com/k3d-io/k3d/v5/pkg/logger"
"github.com/k3d-io/k3d/v5/pkg/runtimes"
k3d "github.com/k3d-io/k3d/v5/pkg/types"
Expand Down
2 changes: 1 addition & 1 deletion cmd/config/configInit.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import (
"fmt"
"os"

config "github.com/k3d-io/k3d/v5/pkg/config/v1alpha4"
config "github.com/k3d-io/k3d/v5/pkg/config/v1alpha5"
l "github.com/k3d-io/k3d/v5/pkg/logger"
"github.com/spf13/cobra"
)
Expand Down
38 changes: 24 additions & 14 deletions cmd/node/nodeCreate.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import (
"github.com/spf13/cobra"

dockerunits "github.com/docker/go-units"
"github.com/k3d-io/k3d/v5/cmd/util"
cliutil "github.com/k3d-io/k3d/v5/cmd/util"
k3dc "github.com/k3d-io/k3d/v5/pkg/client"
l "github.com/k3d-io/k3d/v5/pkg/logger"
Expand Down Expand Up @@ -68,11 +67,11 @@ func NewCmdNodeCreate() *cobra.Command {
// add flags
cmd.Flags().Int("replicas", 1, "Number of replicas of this node specification.")
cmd.Flags().String("role", string(k3d.AgentRole), "Specify node role [server, agent]")
if err := cmd.RegisterFlagCompletionFunc("role", util.ValidArgsNodeRoles); err != nil {
if err := cmd.RegisterFlagCompletionFunc("role", cliutil.ValidArgsNodeRoles); err != nil {
l.Log().Fatalln("Failed to register flag completion for '--role'", err)
}
cmd.Flags().StringP("cluster", "c", k3d.DefaultClusterName, "Cluster URL or k3d cluster name to connect to.")
if err := cmd.RegisterFlagCompletionFunc("cluster", util.ValidArgsAvailableClusters); err != nil {
if err := cmd.RegisterFlagCompletionFunc("cluster", cliutil.ValidArgsAvailableClusters); err != nil {
l.Log().Fatalln("Failed to register flag completion for '--cluster'", err)
}

Expand All @@ -83,6 +82,7 @@ func NewCmdNodeCreate() *cobra.Command {
cmd.Flags().DurationVar(&createNodeOpts.Timeout, "timeout", 0*time.Second, "Maximum waiting time for '--wait' before canceling/returning.")

cmd.Flags().StringSliceP("runtime-label", "", []string{}, "Specify container runtime labels in format \"foo=bar\"")
cmd.Flags().StringSliceP("runtime-ulimit", "", []string{}, "Specify container runtime ulimit in format \"ulimit=soft:hard\"")
cmd.Flags().StringSliceP("k3s-node-label", "", []string{}, "Specify k3s node labels in format \"foo=bar\"")

cmd.Flags().StringSliceP("network", "n", []string{}, "Add node to (another) runtime network")
Expand Down Expand Up @@ -142,8 +142,7 @@ func parseCreateNodeCmd(cmd *cobra.Command, args []string) ([]*k3d.Node, string)
// --runtime-label
runtimeLabelsFlag, err := cmd.Flags().GetStringSlice("runtime-label")
if err != nil {
l.Log().Errorln("No runtime-label specified")
l.Log().Fatalln(err)
l.Log().Fatalf("No runtime-label specified: %v", err)
}

runtimeLabels := make(map[string]string, len(runtimeLabelsFlag)+1)
Expand All @@ -159,6 +158,16 @@ func parseCreateNodeCmd(cmd *cobra.Command, args []string) ([]*k3d.Node, string)
// Internal k3d runtime labels take precedence over user-defined labels
runtimeLabels[k3d.LabelRole] = roleStr

// --runtime-ulimit
runtimeUlimitsFlag, err := cmd.Flags().GetStringSlice("runtime-ulimit")
if err != nil {
l.Log().Fatalf("No runtime-ulimit specified: %v", err)
}

runtimeUlimits := make([]*dockerunits.Ulimit, len(runtimeUlimitsFlag))
for index, ulimit := range runtimeUlimitsFlag {
runtimeUlimits[index] = cliutil.ParseRuntimeUlimit[dockerunits.Ulimit](ulimit)
}
// --k3s-node-label
k3sNodeLabelsFlag, err := cmd.Flags().GetStringSlice("k3s-node-label")
if err != nil {
Expand Down Expand Up @@ -191,15 +200,16 @@ func parseCreateNodeCmd(cmd *cobra.Command, args []string) ([]*k3d.Node, string)
nodes := []*k3d.Node{}
for i := 0; i < replicas; i++ {
node := &k3d.Node{
Name: fmt.Sprintf("%s-%s-%d", k3d.DefaultObjectNamePrefix, args[0], i),
Role: role,
Image: image,
K3sNodeLabels: k3sNodeLabels,
RuntimeLabels: runtimeLabels,
Restart: true,
Memory: memory,
Networks: networks,
Args: k3sArgs,
Name: fmt.Sprintf("%s-%s-%d", k3d.DefaultObjectNamePrefix, args[0], i),
Role: role,
Image: image,
K3sNodeLabels: k3sNodeLabels,
RuntimeLabels: runtimeLabels,
RuntimeUlimits: runtimeUlimits,
Restart: true,
Memory: memory,
Networks: networks,
Args: k3sArgs,
}
nodes = append(nodes, node)
}
Expand Down
110 changes: 110 additions & 0 deletions cmd/util/runtimeUlimits.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
Copyright © 2020-2022 The k3d Author(s)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
package util

import (
"strconv"
"strings"

dockerunits "github.com/docker/go-units"

"github.com/k3d-io/k3d/v5/pkg/config/v1alpha5"
l "github.com/k3d-io/k3d/v5/pkg/logger"
)

type UlimitTypes interface {
dockerunits.Ulimit | v1alpha5.Ulimit
}

type Ulimit[T UlimitTypes] struct {
Values T
}

// ValidateRuntimeUlimitKey validates a given ulimit key is valid
func ValidateRuntimeUlimitKey(ulimitKey string) {
ulimitsKeys := map[string]bool{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we get a list of supported keys from somewhere e.g. in the Docker SDK?
Would be nicer than maintaining it over here.
I'd be fine with it for a start though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be interface{} instead of bool, but I see the benefit of the latter a little further down

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I originally attempt to do so but the map var of it in the go-units is private
https://github.com/docker/go-units/blob/master/ulimit.go#L46

"core": true,
"cpu": true,
"data": true,
"fsize": true,
"locks": true,
"memlock": true,
"msgqueue": true,
"nice": true,
"nofile": true,
"nproc": true,
"rss": true,
"rtprio": true,
"rttime": true,
"sigpending": true,
"stack": true,
}
keysList := make([]string, 0, len(ulimitsKeys))

for key := range ulimitsKeys {
keysList = append(keysList, key)
}
if !ulimitsKeys[ulimitKey] {
l.Log().Fatalf("runtime ulimit \"%s\" is not valid, allowed keys are: %s", ulimitKey, strings.Join(keysList, ", "))
}
}

func ParseRuntimeUlimit[T UlimitTypes](ulimit string) *T {
var parsedUlimit any
var tmpUlimit Ulimit[T]
ulimitSplitted := strings.Split(ulimit, "=")
if len(ulimitSplitted) != 2 {
l.Log().Fatalf("unknown runtime-ulimit format format: %s, use format \"ulimit=soft:hard\"", ulimit)
}
ValidateRuntimeUlimitKey(ulimitSplitted[0])
softHardSplitted := strings.Split(ulimitSplitted[1], ":")
if len(softHardSplitted) != 2 {
l.Log().Fatalf("unknown runtime-ulimit format format: %s, use format \"ulimit=soft:hard\"", ulimit)
}
soft, err := strconv.Atoi(softHardSplitted[0])
if err != nil {
l.Log().Fatalf("unknown runtime-ulimit format format: soft %s has to be int", ulimitSplitted[0])
}
hard, err := strconv.Atoi(softHardSplitted[1])
if err != nil {
l.Log().Fatalf("unknown runtime-ulimit format format: hard %s has to be int", ulimitSplitted[1])
}

switch any(tmpUlimit.Values).(type) {
case dockerunits.Ulimit:
parsedUlimit = &dockerunits.Ulimit{
Name: ulimitSplitted[0],
Soft: int64(soft),
Hard: int64(hard),
}
case v1alpha5.Ulimit:
parsedUlimit = &v1alpha5.Ulimit{
Name: ulimitSplitted[0],
Soft: int64(soft),
Hard: int64(hard),
}
default:
l.Log().Fatalf("Unsupported UlimitTypes, supported types are: dockerunits.Ulimit or v1alpha5.Ulimit")
}

return parsedUlimit.(*T)
}
12 changes: 8 additions & 4 deletions docs/usage/configfile.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ Using a config file is as easy as putting it in a well-known place in your file

As of the time of writing this documentation, the config file only **requires** you to define two fields:

- `apiVersion` to match the version of the config file that you want to use (at this time it would be `apiVersion: k3d.io/v1alpha4`)
- `apiVersion` to match the version of the config file that you want to use (at this time it would be `apiVersion: k3d.io/v1alpha5`)
- `kind` to define the kind of config file that you want to use (currently we only have the `Simple` config)

So this would be the minimal config file, which configures absolutely nothing:

```yaml
apiVersion: k3d.io/v1alpha4
apiVersion: k3d.io/v1alpha5
kind: Simple
```
Expand All @@ -42,15 +42,15 @@ Currently, the config file is still in an Alpha-State, meaning, that it is subje
!!! info "Validation via JSON-Schema"
k3d uses a [JSON-Schema](https://json-schema.org/) to describe the expected format and fields of the configuration file.
This schema is also used to [validate](https://github.com/xeipuuv/gojsonschema#validation) a user-given config file.
This JSON-Schema can be found in the specific config version sub-directory in the repository (e.g. [here for `v1alpha4`](https://github.com/k3d-io/k3d/blob/main/pkg/config/v1alpha4/schema.json)) and could be used to lookup supported fields or by linters to validate the config file, e.g. in your code editor.
This JSON-Schema can be found in the specific config version sub-directory in the repository (e.g. [here for `v1alpha5`](https://github.com/k3d-io/k3d/blob/main/pkg/config/v1alpha5/schema.json)) and could be used to lookup supported fields or by linters to validate the config file, e.g. in your code editor.

### All Options: Example

Since the config options and the config file are changing quite a bit, it's hard to keep track of all the supported config file settings, so here's an example showing all of them as of the time of writing:

```yaml
# k3d configuration file, saved as e.g. /home/me/myk3dcluster.yaml
apiVersion: k3d.io/v1alpha4 # this will change in the future as we make everything more stable
apiVersion: k3d.io/v1alpha5 # this will change in the future as we make everything more stable
kind: Simple # internally, we also have a Cluster config, which is not yet available externally
metadata:
name: mycluster # name that you want to give to your cluster (will still be prefixed with `k3d-`)
Expand Down Expand Up @@ -131,6 +131,10 @@ options:
- label: bar=baz # same as `--runtime-label 'bar=baz@agent:1'` -> this results in a runtime (docker) container label
nodeFilters:
- agent:1
ulimits:
- name: nofile
soft: 26677
hard: 26677

```

Expand Down
4 changes: 2 additions & 2 deletions docs/usage/registries.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ This file can also be used for providing additional information necessary for ac
If you're using a `SimpleConfig` file to configure your k3d cluster, you may as well embed the registries.yaml in there directly:

```yaml
apiVersion: k3d.io/v1alpha4
apiVersion: k3d.io/v1alpha5
kind: Simple
metadata:
name: test
Expand Down Expand Up @@ -252,7 +252,7 @@ Then you should check that the pod is running with `kubectl get pods -l "app=ngi
1. Create a config file, e.g. `/home/me/test-regcache.yaml`

```yaml
apiVersion: k3d.io/v1alpha4
apiVersion: k3d.io/v1alpha5
kind: Simple
metadata:
name: test-regcache
Expand Down
2 changes: 1 addition & 1 deletion pkg/client/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import (
"sigs.k8s.io/yaml"

"github.com/k3d-io/k3d/v5/pkg/actions"
config "github.com/k3d-io/k3d/v5/pkg/config/v1alpha4"
config "github.com/k3d-io/k3d/v5/pkg/config/v1alpha5"
l "github.com/k3d-io/k3d/v5/pkg/logger"
"github.com/k3d-io/k3d/v5/pkg/runtimes"
k3drt "github.com/k3d-io/k3d/v5/pkg/runtimes"
Expand Down
2 changes: 1 addition & 1 deletion pkg/client/ports.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import (
"sigs.k8s.io/yaml"

"github.com/k3d-io/k3d/v5/pkg/config/types"
config "github.com/k3d-io/k3d/v5/pkg/config/v1alpha4"
config "github.com/k3d-io/k3d/v5/pkg/config/v1alpha5"
l "github.com/k3d-io/k3d/v5/pkg/logger"
"github.com/k3d-io/k3d/v5/pkg/runtimes"
k3d "github.com/k3d-io/k3d/v5/pkg/types"
Expand Down
8 changes: 7 additions & 1 deletion pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ import (
"github.com/k3d-io/k3d/v5/pkg/config/v1alpha2"
"github.com/k3d-io/k3d/v5/pkg/config/v1alpha3"
"github.com/k3d-io/k3d/v5/pkg/config/v1alpha4"
defaultConfig "github.com/k3d-io/k3d/v5/pkg/config/v1alpha4"
"github.com/k3d-io/k3d/v5/pkg/config/v1alpha5"
defaultConfig "github.com/k3d-io/k3d/v5/pkg/config/v1alpha5"

types "github.com/k3d-io/k3d/v5/pkg/config/types"
)
Expand All @@ -43,6 +44,7 @@ var Schemas = map[string]string{
v1alpha2.ApiVersion: v1alpha2.JSONSchema,
v1alpha3.ApiVersion: v1alpha3.JSONSchema,
v1alpha4.ApiVersion: v1alpha4.JSONSchema,
v1alpha5.ApiVersion: v1alpha5.JSONSchema,
}

func GetSchemaByVersion(apiVersion string) ([]byte, error) {
Expand Down Expand Up @@ -70,6 +72,8 @@ func FromViper(config *viper.Viper) (types.Config, error) {
cfg, err = v1alpha3.GetConfigByKind(kind)
case "k3d.io/v1alpha4":
cfg, err = v1alpha4.GetConfigByKind(kind)
case "k3d.io/v1alpha5":
cfg, err = v1alpha5.GetConfigByKind(kind)
case "":
cfg, err = defaultConfig.GetConfigByKind(kind)
default:
Expand All @@ -93,6 +97,8 @@ func getMigrations(version string) map[string]func(types.Config) (types.Config,
return v1alpha3.Migrations
case v1alpha4.ApiVersion:
return v1alpha4.Migrations
case v1alpha5.ApiVersion:
return v1alpha5.Migrations
default:
return nil
}
Expand Down
14 changes: 14 additions & 0 deletions pkg/config/config.versions.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,20 @@
"title": "All k3d config versions",
"type": "object",
"oneOf": [
{
"allOf": [
{
"properties": {
"version": {
"const": "v1alpha5"
}
}
},
{
"$ref": "https://raw.githubusercontent.com/k3d-io/k3d/main/pkg/config/v1alpha5/schema.json"
}
]
},
{
"allOf": [
{
Expand Down
Loading