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 3 months ago
parent 976ed8c0be
commit 9a4d5e9212

@ -62,6 +62,7 @@ Environment variables:
| $HELM_REPOSITORY_CONFIG | set the path to the repositories file. |
| $KUBECONFIG | set an alternative Kubernetes configuration file (default "~/.kube/config") |
| $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_KUBEASGROUPS | set the Groups to use for impersonation using a comma-separated list. |
| $HELM_KUBEASUSER | set the Username to impersonate for the operation. |

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

@ -29,6 +29,7 @@ import (
"os"
"strconv"
"strings"
"time"
"github.com/spf13/pflag"
"k8s.io/cli-runtime/pkg/genericclioptions"
@ -64,6 +65,8 @@ type EnvSettings struct {
KubeAsGroups []string
// Kubernetes API Server Endpoint for authentication
KubeAPIServer string
// The timeout on requests towards the Kubernetes API Server Endpoint.
KubeAPITimeout time.Duration
// Custom certificate authority file.
KubeCaFile string
// KubeInsecureSkipTLSVerify indicates if server's certificate will not be checked for validity.
@ -99,6 +102,7 @@ func New() *EnvSettings {
KubeAsUser: os.Getenv("HELM_KUBEASUSER"),
KubeAsGroups: envCSV("HELM_KUBEASGROUPS"),
KubeAPIServer: os.Getenv("HELM_KUBEAPISERVER"),
KubeAPITimeout: envDurationOr("HELM_KUBEAPITIMEOUT", 0),
KubeCaFile: os.Getenv("HELM_KUBECAFILE"),
KubeTLSServerName: os.Getenv("HELM_KUBETLS_SERVER_NAME"),
KubeInsecureSkipTLSVerify: envBoolOr("HELM_KUBEINSECURE_SKIP_TLS_VERIFY", false),
@ -126,6 +130,7 @@ func New() *EnvSettings {
WrapConfigFn: func(config *rest.Config) *rest.Config {
config.Burst = env.BurstLimit
config.QPS = env.QPS
config.Timeout = env.KubeAPITimeout
config.Wrap(func(rt http.RoundTripper) http.RoundTripper {
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.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.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.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")
@ -199,6 +205,18 @@ func envFloat32Or(name string, def float32) float32 {
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) {
trimmed := strings.Trim(os.Getenv(name), ", ")
if trimmed != "" {
@ -207,6 +225,14 @@ func envCSV(name string) (ls []string) {
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 {
envvars := map[string]string{
"HELM_BIN": os.Args[0],
@ -229,6 +255,7 @@ func (s *EnvSettings) EnvVars() map[string]string {
"HELM_KUBEASUSER": s.KubeAsUser,
"HELM_KUBEASGROUPS": strings.Join(s.KubeAsGroups, ","),
"HELM_KUBEAPISERVER": s.KubeAPIServer,
"HELM_KUBEAPITIMEOUT": durationToEnvString(s.KubeAPITimeout),
"HELM_KUBECAFILE": s.KubeCaFile,
"HELM_KUBEINSECURE_SKIP_TLS_VERIFY": strconv.FormatBool(s.KubeInsecureSkipTLSVerify),
"HELM_KUBETLS_SERVER_NAME": s.KubeTLSServerName,

@ -21,6 +21,7 @@ import (
"reflect"
"strings"
"testing"
"time"
"github.com/spf13/pflag"
@ -55,6 +56,7 @@ func TestEnvSettings(t *testing.T) {
maxhistory int
kubeAsUser string
kubeAsGroups []string
kubeTimeout time.Duration
kubeCaFile string
kubeInsecure bool
kubeTLSServer string
@ -70,7 +72,7 @@ func TestEnvSettings(t *testing.T) {
},
{
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",
debug: true,
maxhistory: defaultMaxHistory,
@ -78,13 +80,14 @@ func TestEnvSettings(t *testing.T) {
qps: 50.12,
kubeAsUser: "poro",
kubeAsGroups: []string{"admins", "teatime", "snackeaters"},
kubeTimeout: 10 * time.Second,
kubeCaFile: "/tmp/ca.crt",
kubeTLSServer: "example.org",
kubeInsecure: true,
},
{
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",
maxhistory: 5,
burstLimit: 150,
@ -92,14 +95,15 @@ func TestEnvSettings(t *testing.T) {
debug: true,
kubeAsUser: "pikachu",
kubeAsGroups: []string{"operators", "snackeaters", "partyanimals"},
kubeTimeout: 40 * time.Second,
kubeCaFile: "/tmp/ca.crt",
kubeTLSServer: "example.org",
kubeInsecure: true,
},
{
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",
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"},
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_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",
debug: true,
maxhistory: 5,
@ -107,6 +111,7 @@ func TestEnvSettings(t *testing.T) {
qps: 70,
kubeAsUser: "poro",
kubeAsGroups: []string{"admins", "teatime", "snackeaters"},
kubeTimeout: 45 * time.Second,
kubeCaFile: "/my/ca.crt",
kubeTLSServer: "example.org",
kubeInsecure: true,
@ -145,6 +150,9 @@ func TestEnvSettings(t *testing.T) {
if !reflect.DeepEqual(tt.kubeAsGroups, 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 {
t.Errorf("expected kCaFile %q, got %q", tt.kubeCaFile, settings.KubeCaFile)
}

Loading…
Cancel
Save