Add API to set timeout on requests towards the K8s API server

The new flag "--kube-apitimeout <duration>" or env.var. HELM_KUBEAPITIMEOUT
can be used to set the timeout on requests towards the Kubernetes API server.

This enables users with problematic K8s cluster to override the default timeout
and let the sent requests finish successfully.

Examples usage:

> helm --kube-apitimeout=45s install ...
> HELM_KUBEAPITIMEOUT=45s helm env

Signed-off-by: Björn Svensson <bjorn.a.svensson@est.tech>
pull/12909/head
Björn Svensson 4 months ago
parent 976ed8c0be
commit 9a4d5e9212

@ -62,6 +62,7 @@ Environment variables:
| $HELM_REPOSITORY_CONFIG | set the path to the repositories file. | | $HELM_REPOSITORY_CONFIG | set the path to the repositories file. |
| $KUBECONFIG | set an alternative Kubernetes configuration file (default "~/.kube/config") | | $KUBECONFIG | set an alternative Kubernetes configuration file (default "~/.kube/config") |
| $HELM_KUBEAPISERVER | set the Kubernetes API Server Endpoint for authentication | | $HELM_KUBEAPISERVER | set the Kubernetes API Server Endpoint for authentication |
| $HELM_KUBEAPITIMEOUT | set the timeout on requests towards the Kubernetes API Server. Requires a valid time unit (e.g. 30s, 1m). |
| $HELM_KUBECAFILE | set the Kubernetes certificate authority file. | | $HELM_KUBECAFILE | set the Kubernetes certificate authority file. |
| $HELM_KUBEASGROUPS | set the Groups to use for impersonation using a comma-separated list. | | $HELM_KUBEASGROUPS | set the Groups to use for impersonation using a comma-separated list. |
| $HELM_KUBEASUSER | set the Username to impersonate for the operation. | | $HELM_KUBEASUSER | set the Username to impersonate for the operation. |

@ -5,6 +5,7 @@ HELM_CONFIG_HOME
HELM_DATA_HOME HELM_DATA_HOME
HELM_DEBUG HELM_DEBUG
HELM_KUBEAPISERVER HELM_KUBEAPISERVER
HELM_KUBEAPITIMEOUT
HELM_KUBEASGROUPS HELM_KUBEASGROUPS
HELM_KUBEASUSER HELM_KUBEASUSER
HELM_KUBECAFILE HELM_KUBECAFILE

@ -29,6 +29,7 @@ import (
"os" "os"
"strconv" "strconv"
"strings" "strings"
"time"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/genericclioptions"
@ -64,6 +65,8 @@ type EnvSettings struct {
KubeAsGroups []string KubeAsGroups []string
// Kubernetes API Server Endpoint for authentication // Kubernetes API Server Endpoint for authentication
KubeAPIServer string KubeAPIServer string
// The timeout on requests towards the Kubernetes API Server Endpoint.
KubeAPITimeout time.Duration
// Custom certificate authority file. // Custom certificate authority file.
KubeCaFile string KubeCaFile string
// KubeInsecureSkipTLSVerify indicates if server's certificate will not be checked for validity. // KubeInsecureSkipTLSVerify indicates if server's certificate will not be checked for validity.
@ -99,6 +102,7 @@ func New() *EnvSettings {
KubeAsUser: os.Getenv("HELM_KUBEASUSER"), KubeAsUser: os.Getenv("HELM_KUBEASUSER"),
KubeAsGroups: envCSV("HELM_KUBEASGROUPS"), KubeAsGroups: envCSV("HELM_KUBEASGROUPS"),
KubeAPIServer: os.Getenv("HELM_KUBEAPISERVER"), KubeAPIServer: os.Getenv("HELM_KUBEAPISERVER"),
KubeAPITimeout: envDurationOr("HELM_KUBEAPITIMEOUT", 0),
KubeCaFile: os.Getenv("HELM_KUBECAFILE"), KubeCaFile: os.Getenv("HELM_KUBECAFILE"),
KubeTLSServerName: os.Getenv("HELM_KUBETLS_SERVER_NAME"), KubeTLSServerName: os.Getenv("HELM_KUBETLS_SERVER_NAME"),
KubeInsecureSkipTLSVerify: envBoolOr("HELM_KUBEINSECURE_SKIP_TLS_VERIFY", false), KubeInsecureSkipTLSVerify: envBoolOr("HELM_KUBEINSECURE_SKIP_TLS_VERIFY", false),
@ -126,6 +130,7 @@ func New() *EnvSettings {
WrapConfigFn: func(config *rest.Config) *rest.Config { WrapConfigFn: func(config *rest.Config) *rest.Config {
config.Burst = env.BurstLimit config.Burst = env.BurstLimit
config.QPS = env.QPS config.QPS = env.QPS
config.Timeout = env.KubeAPITimeout
config.Wrap(func(rt http.RoundTripper) http.RoundTripper { config.Wrap(func(rt http.RoundTripper) http.RoundTripper {
return &retryingRoundTripper{wrapped: rt} return &retryingRoundTripper{wrapped: rt}
}) })
@ -145,6 +150,7 @@ func (s *EnvSettings) AddFlags(fs *pflag.FlagSet) {
fs.StringVar(&s.KubeAsUser, "kube-as-user", s.KubeAsUser, "username to impersonate for the operation") fs.StringVar(&s.KubeAsUser, "kube-as-user", s.KubeAsUser, "username to impersonate for the operation")
fs.StringArrayVar(&s.KubeAsGroups, "kube-as-group", s.KubeAsGroups, "group to impersonate for the operation, this flag can be repeated to specify multiple groups.") fs.StringArrayVar(&s.KubeAsGroups, "kube-as-group", s.KubeAsGroups, "group to impersonate for the operation, this flag can be repeated to specify multiple groups.")
fs.StringVar(&s.KubeAPIServer, "kube-apiserver", s.KubeAPIServer, "the address and the port for the Kubernetes API server") fs.StringVar(&s.KubeAPIServer, "kube-apiserver", s.KubeAPIServer, "the address and the port for the Kubernetes API server")
fs.DurationVar(&s.KubeAPITimeout, "kube-apitimeout", s.KubeAPITimeout, "the timeout on requests towards the Kubernetes API server")
fs.StringVar(&s.KubeCaFile, "kube-ca-file", s.KubeCaFile, "the certificate authority file for the Kubernetes API server connection") fs.StringVar(&s.KubeCaFile, "kube-ca-file", s.KubeCaFile, "the certificate authority file for the Kubernetes API server connection")
fs.StringVar(&s.KubeTLSServerName, "kube-tls-server-name", s.KubeTLSServerName, "server name to use for Kubernetes API server certificate validation. If it is not provided, the hostname used to contact the server is used") fs.StringVar(&s.KubeTLSServerName, "kube-tls-server-name", s.KubeTLSServerName, "server name to use for Kubernetes API server certificate validation. If it is not provided, the hostname used to contact the server is used")
fs.BoolVar(&s.KubeInsecureSkipTLSVerify, "kube-insecure-skip-tls-verify", s.KubeInsecureSkipTLSVerify, "if true, the Kubernetes API server's certificate will not be checked for validity. This will make your HTTPS connections insecure") fs.BoolVar(&s.KubeInsecureSkipTLSVerify, "kube-insecure-skip-tls-verify", s.KubeInsecureSkipTLSVerify, "if true, the Kubernetes API server's certificate will not be checked for validity. This will make your HTTPS connections insecure")
@ -199,6 +205,18 @@ func envFloat32Or(name string, def float32) float32 {
return float32(ret) return float32(ret)
} }
func envDurationOr(name string, def time.Duration) time.Duration {
if name == "" {
return def
}
envVal := envOr(name, def.String())
ret, err := time.ParseDuration(envVal)
if err != nil {
return def
}
return ret
}
func envCSV(name string) (ls []string) { func envCSV(name string) (ls []string) {
trimmed := strings.Trim(os.Getenv(name), ", ") trimmed := strings.Trim(os.Getenv(name), ", ")
if trimmed != "" { if trimmed != "" {
@ -207,6 +225,14 @@ func envCSV(name string) (ls []string) {
return return
} }
// An unconfigured duration is presented as an empty string instead of '0s'.
func durationToEnvString(value time.Duration) string {
if value == 0 {
return ""
}
return value.String()
}
func (s *EnvSettings) EnvVars() map[string]string { func (s *EnvSettings) EnvVars() map[string]string {
envvars := map[string]string{ envvars := map[string]string{
"HELM_BIN": os.Args[0], "HELM_BIN": os.Args[0],
@ -229,6 +255,7 @@ func (s *EnvSettings) EnvVars() map[string]string {
"HELM_KUBEASUSER": s.KubeAsUser, "HELM_KUBEASUSER": s.KubeAsUser,
"HELM_KUBEASGROUPS": strings.Join(s.KubeAsGroups, ","), "HELM_KUBEASGROUPS": strings.Join(s.KubeAsGroups, ","),
"HELM_KUBEAPISERVER": s.KubeAPIServer, "HELM_KUBEAPISERVER": s.KubeAPIServer,
"HELM_KUBEAPITIMEOUT": durationToEnvString(s.KubeAPITimeout),
"HELM_KUBECAFILE": s.KubeCaFile, "HELM_KUBECAFILE": s.KubeCaFile,
"HELM_KUBEINSECURE_SKIP_TLS_VERIFY": strconv.FormatBool(s.KubeInsecureSkipTLSVerify), "HELM_KUBEINSECURE_SKIP_TLS_VERIFY": strconv.FormatBool(s.KubeInsecureSkipTLSVerify),
"HELM_KUBETLS_SERVER_NAME": s.KubeTLSServerName, "HELM_KUBETLS_SERVER_NAME": s.KubeTLSServerName,

@ -21,6 +21,7 @@ import (
"reflect" "reflect"
"strings" "strings"
"testing" "testing"
"time"
"github.com/spf13/pflag" "github.com/spf13/pflag"
@ -55,6 +56,7 @@ func TestEnvSettings(t *testing.T) {
maxhistory int maxhistory int
kubeAsUser string kubeAsUser string
kubeAsGroups []string kubeAsGroups []string
kubeTimeout time.Duration
kubeCaFile string kubeCaFile string
kubeInsecure bool kubeInsecure bool
kubeTLSServer string kubeTLSServer string
@ -70,7 +72,7 @@ func TestEnvSettings(t *testing.T) {
}, },
{ {
name: "with flags set", name: "with flags set",
args: "--debug --namespace=myns --kube-as-user=poro --kube-as-group=admins --kube-as-group=teatime --kube-as-group=snackeaters --kube-ca-file=/tmp/ca.crt --burst-limit 100 --qps 50.12 --kube-insecure-skip-tls-verify=true --kube-tls-server-name=example.org", args: "--debug --namespace=myns --kube-as-user=poro --kube-as-group=admins --kube-as-group=teatime --kube-as-group=snackeaters --kube-apitimeout=10s --kube-ca-file=/tmp/ca.crt --burst-limit 100 --qps 50.12 --kube-insecure-skip-tls-verify=true --kube-tls-server-name=example.org",
ns: "myns", ns: "myns",
debug: true, debug: true,
maxhistory: defaultMaxHistory, maxhistory: defaultMaxHistory,
@ -78,13 +80,14 @@ func TestEnvSettings(t *testing.T) {
qps: 50.12, qps: 50.12,
kubeAsUser: "poro", kubeAsUser: "poro",
kubeAsGroups: []string{"admins", "teatime", "snackeaters"}, kubeAsGroups: []string{"admins", "teatime", "snackeaters"},
kubeTimeout: 10 * time.Second,
kubeCaFile: "/tmp/ca.crt", kubeCaFile: "/tmp/ca.crt",
kubeTLSServer: "example.org", kubeTLSServer: "example.org",
kubeInsecure: true, kubeInsecure: true,
}, },
{ {
name: "with envvars set", name: "with envvars set",
envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_KUBEASUSER": "pikachu", "HELM_KUBEASGROUPS": ",,,operators,snackeaters,partyanimals", "HELM_MAX_HISTORY": "5", "HELM_KUBECAFILE": "/tmp/ca.crt", "HELM_BURST_LIMIT": "150", "HELM_KUBEINSECURE_SKIP_TLS_VERIFY": "true", "HELM_KUBETLS_SERVER_NAME": "example.org", "HELM_QPS": "60.34"}, envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_KUBEASUSER": "pikachu", "HELM_KUBEASGROUPS": ",,,operators,snackeaters,partyanimals", "HELM_KUBEAPITIMEOUT": "40s", "HELM_MAX_HISTORY": "5", "HELM_KUBECAFILE": "/tmp/ca.crt", "HELM_BURST_LIMIT": "150", "HELM_KUBEINSECURE_SKIP_TLS_VERIFY": "true", "HELM_KUBETLS_SERVER_NAME": "example.org", "HELM_QPS": "60.34"},
ns: "yourns", ns: "yourns",
maxhistory: 5, maxhistory: 5,
burstLimit: 150, burstLimit: 150,
@ -92,14 +95,15 @@ func TestEnvSettings(t *testing.T) {
debug: true, debug: true,
kubeAsUser: "pikachu", kubeAsUser: "pikachu",
kubeAsGroups: []string{"operators", "snackeaters", "partyanimals"}, kubeAsGroups: []string{"operators", "snackeaters", "partyanimals"},
kubeTimeout: 40 * time.Second,
kubeCaFile: "/tmp/ca.crt", kubeCaFile: "/tmp/ca.crt",
kubeTLSServer: "example.org", kubeTLSServer: "example.org",
kubeInsecure: true, kubeInsecure: true,
}, },
{ {
name: "with flags and envvars set", name: "with flags and envvars set",
args: "--debug --namespace=myns --kube-as-user=poro --kube-as-group=admins --kube-as-group=teatime --kube-as-group=snackeaters --kube-ca-file=/my/ca.crt --burst-limit 175 --qps 70 --kube-insecure-skip-tls-verify=true --kube-tls-server-name=example.org", args: "--debug --namespace=myns --kube-as-user=poro --kube-as-group=admins --kube-as-group=teatime --kube-as-group=snackeaters --kube-apitimeout=45s --kube-ca-file=/my/ca.crt --burst-limit 175 --qps 70 --kube-insecure-skip-tls-verify=true --kube-tls-server-name=example.org",
envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_KUBEASUSER": "pikachu", "HELM_KUBEASGROUPS": ",,,operators,snackeaters,partyanimals", "HELM_MAX_HISTORY": "5", "HELM_KUBECAFILE": "/tmp/ca.crt", "HELM_BURST_LIMIT": "200", "HELM_KUBEINSECURE_SKIP_TLS_VERIFY": "true", "HELM_KUBETLS_SERVER_NAME": "example.org", "HELM_QPS": "40"}, envvars: map[string]string{"HELM_DEBUG": "1", "HELM_NAMESPACE": "yourns", "HELM_KUBEASUSER": "pikachu", "HELM_KUBEASGROUPS": ",,,operators,snackeaters,partyanimals", "HELM_KUBEAPITIMEOUT": "1m30s", "HELM_MAX_HISTORY": "5", "HELM_KUBECAFILE": "/tmp/ca.crt", "HELM_BURST_LIMIT": "200", "HELM_KUBEINSECURE_SKIP_TLS_VERIFY": "true", "HELM_KUBETLS_SERVER_NAME": "example.org", "HELM_QPS": "40"},
ns: "myns", ns: "myns",
debug: true, debug: true,
maxhistory: 5, maxhistory: 5,
@ -107,6 +111,7 @@ func TestEnvSettings(t *testing.T) {
qps: 70, qps: 70,
kubeAsUser: "poro", kubeAsUser: "poro",
kubeAsGroups: []string{"admins", "teatime", "snackeaters"}, kubeAsGroups: []string{"admins", "teatime", "snackeaters"},
kubeTimeout: 45 * time.Second,
kubeCaFile: "/my/ca.crt", kubeCaFile: "/my/ca.crt",
kubeTLSServer: "example.org", kubeTLSServer: "example.org",
kubeInsecure: true, kubeInsecure: true,
@ -145,6 +150,9 @@ func TestEnvSettings(t *testing.T) {
if !reflect.DeepEqual(tt.kubeAsGroups, settings.KubeAsGroups) { if !reflect.DeepEqual(tt.kubeAsGroups, settings.KubeAsGroups) {
t.Errorf("expected kAsGroups %+v, got %+v", len(tt.kubeAsGroups), len(settings.KubeAsGroups)) t.Errorf("expected kAsGroups %+v, got %+v", len(tt.kubeAsGroups), len(settings.KubeAsGroups))
} }
if tt.kubeTimeout != settings.KubeAPITimeout {
t.Errorf("expected KubeAPITimeout %q, got %q", tt.kubeTimeout, settings.KubeAPITimeout)
}
if tt.kubeCaFile != settings.KubeCaFile { if tt.kubeCaFile != settings.KubeCaFile {
t.Errorf("expected kCaFile %q, got %q", tt.kubeCaFile, settings.KubeCaFile) t.Errorf("expected kCaFile %q, got %q", tt.kubeCaFile, settings.KubeCaFile)
} }

Loading…
Cancel
Save