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

config: prepare for introducing config file handling #248

Merged
merged 3 commits into from
Jan 22, 2024
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
8 changes: 4 additions & 4 deletions cmd/resource-topology-exporter/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func main() {
os.Exit(0)
}

k8scli, err := k8shelpers.GetK8sClient(parsedArgs.KubeConfig)
k8scli, err := k8shelpers.GetK8sClient(parsedArgs.Global.KubeConfig)
if err != nil {
klog.Fatalf("failed to get k8s client: %v", err)
}
Expand All @@ -64,15 +64,15 @@ func main() {
}
defer cleanup()

cli = sharedcpuspool.NewFromLister(cli, parsedArgs.RTE.Debug, parsedArgs.RTE.ReferenceContainer)
cli = sharedcpuspool.NewFromLister(cli, parsedArgs.Global.Debug, parsedArgs.RTE.ReferenceContainer)

if len(parsedArgs.Resourcemonitor.PodExclude) > 0 {
cli = podexclude.NewFromLister(cli, parsedArgs.RTE.Debug, parsedArgs.Resourcemonitor.PodExclude)
cli = podexclude.NewFromLister(cli, parsedArgs.Global.Debug, parsedArgs.Resourcemonitor.PodExclude)
}

if parsedArgs.Resourcemonitor.ExcludeTerminalPods {
klog.Infof("terminal pods are filtered from the PodResourcesLister client")
cli, err = terminalpods.NewFromLister(context.TODO(), cli, k8scli, time.Minute, parsedArgs.RTE.Debug)
cli, err = terminalpods.NewFromLister(context.TODO(), cli, k8scli, time.Minute, parsedArgs.Global.Debug)
if err != nil {
klog.Fatalf("failed to get PodResourceAPI client: %w", err)
}
Expand Down
192 changes: 25 additions & 167 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,33 +18,36 @@ package config

import (
"encoding/json"
"errors"
"flag"
"fmt"
"os"
"time"

"k8s.io/klog/v2"
"sigs.k8s.io/yaml"

"github.com/k8stopologyawareschedwg/podfingerprint"

metricssrv "github.com/k8stopologyawareschedwg/resource-topology-exporter/pkg/metrics/server"
"github.com/k8stopologyawareschedwg/resource-topology-exporter/pkg/nrtupdater"
"github.com/k8stopologyawareschedwg/resource-topology-exporter/pkg/podres/middleware/podexclude"
"github.com/k8stopologyawareschedwg/resource-topology-exporter/pkg/podres/middleware/sharedcpuspool"
"github.com/k8stopologyawareschedwg/resource-topology-exporter/pkg/resourcemonitor"
"github.com/k8stopologyawareschedwg/resource-topology-exporter/pkg/resourcetopologyexporter"
"github.com/k8stopologyawareschedwg/resource-topology-exporter/pkg/version"
)

type GlobalArgs struct {
KubeConfig string `json:"kubeConfig,omitempty"`
Debug bool `json:"debug,omitempty"`
Verbose int `json:"verbose"`
}

func (args GlobalArgs) Clone() GlobalArgs {
return GlobalArgs{
KubeConfig: args.KubeConfig,
Debug: args.Debug,
Verbose: args.Verbose,
}
}

type ProgArgs struct {
NRTupdater nrtupdater.Args
Resourcemonitor resourcemonitor.Args
RTE resourcetopologyexporter.Args
Version bool
DumpConfig string
KubeConfig string
Global GlobalArgs `json:"global,omitempty"`
NRTupdater nrtupdater.Args `json:"nrtUpdater,omitempty"`
Resourcemonitor resourcemonitor.Args `json:"resourceMonitor,omitempty"`
RTE resourcetopologyexporter.Args `json:"topologyExporter,omitempty"`
Version bool `json:"-"`
DumpConfig string `json:"-"`
}

func (pa *ProgArgs) ToJson() ([]byte, error) {
Expand All @@ -55,155 +58,10 @@ func (pa *ProgArgs) ToYaml() ([]byte, error) {
return yaml.Marshal(pa)
}

type kubeletParams struct {
TopologyManagerPolicy string `json:"topologyManagerPolicy,omitempty"`
TopologyManagerScope string `json:"topologyManagerScope,omitempty"`
}

type config struct {
Kubelet kubeletParams `json:"kubelet,omitempty"`
ResourceExclude resourcemonitor.ResourceExclude `json:"resourceExclude,omitempty"`
PodExclude podexclude.List `json:"podExclude,omitempty"`
}

func readConfig(configPath string) (config, error) {
conf := config{}
data, err := os.ReadFile(configPath)
if err != nil {
// config is optional
if errors.Is(err, os.ErrNotExist) {
klog.Infof("couldn't find configuration in %q", configPath)
return conf, nil
}
return conf, err
}
err = yaml.Unmarshal(data, &conf)
return conf, err
}

// The args is passed only for testing purposes.
func LoadArgs(args ...string) (ProgArgs, error) {
var pArgs ProgArgs

var configPath string
var pfpMethod string
var metricsMode string
flags := flag.NewFlagSet(version.ProgramName, flag.ExitOnError)

klog.InitFlags(flags)

flags.BoolVar(&pArgs.NRTupdater.NoPublish, "no-publish", false, "Do not publish discovered features to the cluster-local Kubernetes API server.")
flags.BoolVar(&pArgs.NRTupdater.Oneshot, "oneshot", false, "Update once and exit.")
flags.StringVar(&pArgs.NRTupdater.Hostname, "hostname", DefaultHostName(), "Override the node hostname.")

flags.StringVar(&pArgs.Resourcemonitor.Namespace, "watch-namespace", "", "Namespace to watch pods for. Use \"\" for all namespaces.")
flags.StringVar(&pArgs.Resourcemonitor.SysfsRoot, "sysfs", "/sys", "Top-level component path of sysfs.")
flags.BoolVar(&pArgs.Resourcemonitor.PodSetFingerprint, "pods-fingerprint", true, "If enable, compute and report the pod set fingerprint.")
flags.BoolVar(&pArgs.Resourcemonitor.ExposeTiming, "expose-timing", false, "If enable, expose expected and actual sleep interval as annotations.")
flags.BoolVar(&pArgs.Resourcemonitor.RefreshNodeResources, "refresh-node-resources", false, "If enable, track changes in node's resources")
flags.StringVar(&pArgs.Resourcemonitor.PodSetFingerprintStatusFile, "pods-fingerprint-status-file", "", "File to dump the pods fingerprint status. Use empty string to disable.")
flags.BoolVar(&pArgs.Resourcemonitor.ExcludeTerminalPods, "exclude-terminal-pods", false, "If enable, exclude terminal pods from podresource API List call")
flags.StringVar(&pfpMethod, "pods-fingerprint-method", podfingerprint.MethodWithExclusiveResources, fmt.Sprintf("Select the method to compute the pods fingerprint. Valid options: %s.", resourcemonitor.PFPMethodSupported()))

flags.StringVar(&configPath, "config", "/etc/resource-topology-exporter/config.yaml", "Configuration file path. Use this to set the exclude list.")

flags.BoolVar(&pArgs.RTE.Debug, "debug", false, " Enable debug output.")
flags.StringVar(&pArgs.RTE.TopologyManagerPolicy, "topology-manager-policy", DefaultTopologyManagerPolicy(), "Explicitly set the topology manager policy instead of reading from the kubelet.")
flags.StringVar(&pArgs.RTE.TopologyManagerScope, "topology-manager-scope", DefaultTopologyManagerScope(), "Explicitly set the topology manager scope instead of reading from the kubelet.")
flags.DurationVar(&pArgs.RTE.SleepInterval, "sleep-interval", 60*time.Second, "Time to sleep between podresources API polls. Set to zero to completely disable the polling.")
flags.StringVar(&pArgs.RTE.KubeletConfigFile, "kubelet-config-file", "/podresources/config.yaml", "Kubelet config file path.")
flags.StringVar(&pArgs.RTE.PodResourcesSocketPath, "podresources-socket", "unix:///podresources/kubelet.sock", "Pod Resource Socket path to use.")
flags.BoolVar(&pArgs.RTE.PodReadinessEnable, "podreadiness", true, "Custom condition injection using Podreadiness.")
flags.BoolVar(&pArgs.RTE.AddNRTOwnerEnable, "add-nrt-owner", true, "RTE will inject NRT's related node as OwnerReference to ensure cleanup if the node is deleted.")
flags.StringVar(&metricsMode, "metrics-mode", metricssrv.ServingDisabled, fmt.Sprintf("Select the mode to expose metrics endpoint. Valid options: %s", metricssrv.ServingModeSupported()))

refCnt := flags.String("reference-container", "", "Reference container, used to learn about the shared cpu pool\n See: https://github.com/kubernetes/kubernetes/issues/102190\n format of spec is namespace/podname/containername.\n Alternatively, you can use the env vars REFERENCE_NAMESPACE, REFERENCE_POD_NAME, REFERENCE_CONTAINER_NAME.")

flags.StringVar(&pArgs.RTE.NotifyFilePath, "notify-file", "", "Notification file path.")
// Lets keep it simple by now and expose only "events-per-second"
// but logic is prepared to be able to also define the time base
// that is why TimeUnitToLimitEvents is hard initialized to Second
flags.Int64Var(&pArgs.RTE.MaxEventsPerTimeUnit, "max-events-per-second", 1, "Max times per second resources will be scanned and updated")
pArgs.RTE.TimeUnitToLimitEvents = time.Second

flags.BoolVar(&pArgs.Version, "version", false, "Output version and exit")
flags.StringVar(&pArgs.DumpConfig, "dump-config", "",
`dump the current configuration to the given file path. Empty string (default) disable the dumping.
Special targets:
. "-" for stdout.
. ".andexit" stdout and exit right after.
. ".log" to dump in the log".`,
)
flags.StringVar(&pArgs.KubeConfig, "kubeconfig", "", "path to kubeconfig file.")

err := flags.Parse(args)
if err != nil {
return pArgs, err
}

if pArgs.Version {
return pArgs, err
}

pArgs.RTE.ReferenceContainer, err = setContainerIdent(*refCnt)
if err != nil {
return pArgs, err
}
if pArgs.RTE.ReferenceContainer.IsEmpty() {
pArgs.RTE.ReferenceContainer = sharedcpuspool.ContainerIdentFromEnv()
}

pArgs.RTE.MetricsMode, err = metricssrv.ServingModeIsSupported(metricsMode)
if err != nil {
return pArgs, err
}

pArgs.Resourcemonitor.PodSetFingerprintMethod, err = resourcemonitor.PFPMethodIsSupported(pfpMethod)
if err != nil {
return pArgs, err
}

conf, err := readConfig(configPath)
if err != nil {
return pArgs, fmt.Errorf("error getting exclude list from the configuration: %w", err)
}

err = setupArgsFromConfig(&pArgs, conf)
return pArgs, err
}

func setupArgsFromConfig(pArgs *ProgArgs, conf config) error {
if len(conf.ResourceExclude) > 0 {
pArgs.Resourcemonitor.ResourceExclude = conf.ResourceExclude
klog.V(2).Infof("using resources exclude:\n%s", pArgs.Resourcemonitor.ResourceExclude.String())
func Finalize(pArgs *ProgArgs) error {
var err error
if pArgs.NRTupdater.Hostname == "" {
pArgs.NRTupdater.Hostname, err = os.Hostname()
}

if len(conf.PodExclude) > 0 {
pArgs.Resourcemonitor.PodExclude = conf.PodExclude
klog.V(2).Infof("using pod excludes:\n%s", pArgs.Resourcemonitor.PodExclude.String())
}

if pArgs.RTE.TopologyManagerPolicy == "" {
pArgs.RTE.TopologyManagerPolicy = conf.Kubelet.TopologyManagerPolicy
klog.V(2).Infof("using kubelet topology manager policy: %q", pArgs.RTE.TopologyManagerPolicy)
}
if pArgs.RTE.TopologyManagerScope == "" {
pArgs.RTE.TopologyManagerScope = conf.Kubelet.TopologyManagerScope
klog.V(2).Infof("using kubelet topology manager scope: %q", pArgs.RTE.TopologyManagerScope)
}

return nil
}

func setContainerIdent(value string) (*sharedcpuspool.ContainerIdent, error) {
ci, err := sharedcpuspool.ContainerIdentFromString(value)
if err != nil {
return nil, err
}

if ci == nil {
return &sharedcpuspool.ContainerIdent{}, nil
}

return ci, nil
return err
}
54 changes: 54 additions & 0 deletions pkg/config/configfile.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
Copyright 2022 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package config

import (
"errors"
"os"

"k8s.io/klog/v2"
"sigs.k8s.io/yaml"

"github.com/k8stopologyawareschedwg/resource-topology-exporter/pkg/podres/middleware/podexclude"
"github.com/k8stopologyawareschedwg/resource-topology-exporter/pkg/resourcemonitor"
)

type kubeletParams struct {
TopologyManagerPolicy string `json:"topologyManagerPolicy,omitempty"`
TopologyManagerScope string `json:"topologyManagerScope,omitempty"`
}

type config struct {
Kubelet kubeletParams `json:"kubelet,omitempty"`
ResourceExclude resourcemonitor.ResourceExclude `json:"resourceExclude,omitempty"`
PodExclude podexclude.List `json:"podExclude,omitempty"`
}

func readExtraConfig(configPath string) (config, error) {
conf := config{}
data, err := os.ReadFile(configPath)
if err != nil {
// config is optional
if errors.Is(err, os.ErrNotExist) {
klog.Infof("couldn't find configuration in %q", configPath)
return conf, nil
}
return conf, err
}
err = yaml.Unmarshal(data, &conf)
return conf, err
}
47 changes: 19 additions & 28 deletions pkg/config/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,36 +17,27 @@ limitations under the License.
package config

import (
"os"
"time"

"k8s.io/klog/v2"
"github.com/k8stopologyawareschedwg/podfingerprint"
metricssrv "github.com/k8stopologyawareschedwg/resource-topology-exporter/pkg/metrics/server"
)

func DefaultHostName() string {
var err error

val, ok := os.LookupEnv("NODE_NAME")
if !ok || val == "" {
val, err = os.Hostname()
if err != nil {
klog.Fatalf("error getting the host name: %v", err)
}
}
return val
}

func DefaultTopologyManagerPolicy() string {
if val, ok := os.LookupEnv("TOPOLOGY_MANAGER_POLICY"); ok {
return val
}
// empty string is a valid value here, so just keep going
return ""
}
const (
DefaultConfigRoot = "/etc/rte"
)

func DefaultTopologyManagerScope() string {
if val, ok := os.LookupEnv("TOPOLOGY_MANAGER_SCOPE"); ok {
return val
}
// empty string is a valid value here, so just keep going
return ""
func SetDefaults(pArgs *ProgArgs) {
pArgs.Global.Verbose = 2
pArgs.Resourcemonitor.SysfsRoot = "/sys"
pArgs.Resourcemonitor.PodSetFingerprint = true
pArgs.Resourcemonitor.PodSetFingerprintMethod = podfingerprint.MethodWithExclusiveResources
pArgs.RTE.SleepInterval = 60 * time.Second
pArgs.RTE.KubeletConfigFile = "/podresources/config.yaml"
pArgs.RTE.PodResourcesSocketPath = "unix:///podresources/kubelet.sock"
pArgs.RTE.PodReadinessEnable = true
pArgs.RTE.AddNRTOwnerEnable = true
pArgs.RTE.MetricsMode = metricssrv.ServingDisabled
pArgs.RTE.MaxEventsPerTimeUnit = 1
pArgs.RTE.TimeUnitToLimitEvents = time.Second
}
Loading