mirror of https://github.com/linkerd/linkerd2.git
Add --single-namespace install flag for restricted permissions (#1721)
* Add --single-namespace install flag for restricted permissions * Better formatting in install template * Mark --single-namespace and --proxy-auto-inject as experimental * Fix wording of --single-namespace check flag * Small healthcheck refactor Signed-off-by: Kevin Lingerfelt <kl@buoyant.io>
This commit is contained in:
parent
8f4240125e
commit
46c887ca00
|
|
@ -21,6 +21,7 @@ type checkOptions struct {
|
|||
dataPlaneOnly bool
|
||||
wait time.Duration
|
||||
namespace string
|
||||
singleNamespace bool
|
||||
}
|
||||
|
||||
func newCheckOptions() *checkOptions {
|
||||
|
|
@ -30,6 +31,7 @@ func newCheckOptions() *checkOptions {
|
|||
dataPlaneOnly: false,
|
||||
wait: 300 * time.Second,
|
||||
namespace: "",
|
||||
singleNamespace: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -65,6 +67,7 @@ non-zero exit code.`,
|
|||
cmd.PersistentFlags().BoolVar(&options.dataPlaneOnly, "proxy", options.dataPlaneOnly, "Only run data-plane checks, to determine if the data plane is healthy")
|
||||
cmd.PersistentFlags().DurationVar(&options.wait, "wait", options.wait, "Retry and wait for some checks to succeed if they don't pass the first time")
|
||||
cmd.PersistentFlags().StringVarP(&options.namespace, "namespace", "n", options.namespace, "Namespace to use for --proxy checks (default: all namespaces)")
|
||||
cmd.PersistentFlags().BoolVar(&options.singleNamespace, "single-namespace", options.singleNamespace, "When running pre-installation checks (--pre), only check the permissions required to operate the control plane in a single namespace")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
|
@ -94,6 +97,7 @@ func configureAndRunChecks(options *checkOptions) {
|
|||
ShouldCheckKubeVersion: true,
|
||||
ShouldCheckControlPlaneVersion: !(options.preInstallOnly || options.dataPlaneOnly),
|
||||
ShouldCheckDataPlaneVersion: options.dataPlaneOnly,
|
||||
SingleNamespace: options.singleNamespace,
|
||||
})
|
||||
|
||||
success := runChecks(os.Stdout, hc)
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ type installConfig struct {
|
|||
ProxyResourceRequestCPU string
|
||||
ProxyResourceRequestMemory string
|
||||
ProxyBindTimeout string
|
||||
SingleNamespace bool
|
||||
}
|
||||
|
||||
type installOptions struct {
|
||||
|
|
@ -66,6 +67,7 @@ type installOptions struct {
|
|||
prometheusReplicas uint
|
||||
controllerLogLevel string
|
||||
proxyAutoInject bool
|
||||
singleNamespace bool
|
||||
*proxyConfigOptions
|
||||
}
|
||||
|
||||
|
|
@ -78,6 +80,7 @@ func newInstallOptions() *installOptions {
|
|||
prometheusReplicas: 1,
|
||||
controllerLogLevel: "info",
|
||||
proxyAutoInject: false,
|
||||
singleNamespace: false,
|
||||
proxyConfigOptions: newProxyConfigOptions(),
|
||||
}
|
||||
}
|
||||
|
|
@ -104,13 +107,14 @@ func newCmdInstall() *cobra.Command {
|
|||
cmd.PersistentFlags().UintVar(&options.webReplicas, "web-replicas", options.webReplicas, "Replicas of the web server to deploy")
|
||||
cmd.PersistentFlags().UintVar(&options.prometheusReplicas, "prometheus-replicas", options.prometheusReplicas, "Replicas of prometheus to deploy")
|
||||
cmd.PersistentFlags().StringVar(&options.controllerLogLevel, "controller-log-level", options.controllerLogLevel, "Log level for the controller and web components")
|
||||
cmd.PersistentFlags().BoolVar(&options.proxyAutoInject, "proxy-auto-inject", options.proxyAutoInject, "Enable proxy sidecar auto-injection webhook; default to false")
|
||||
cmd.PersistentFlags().BoolVar(&options.proxyAutoInject, "proxy-auto-inject", options.proxyAutoInject, "Experimental: Enable proxy sidecar auto-injection webhook (default false)")
|
||||
cmd.PersistentFlags().BoolVar(&options.singleNamespace, "single-namespace", options.singleNamespace, "Experimental: Configure the control plane to only operate in the installed namespace (default false)")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func validateAndBuildConfig(options *installOptions) (*installConfig, error) {
|
||||
if err := validate(options); err != nil {
|
||||
if err := options.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
@ -168,6 +172,7 @@ func validateAndBuildConfig(options *installOptions) (*installConfig, error) {
|
|||
ProxyResourceRequestCPU: options.proxyCpuRequest,
|
||||
ProxyResourceRequestMemory: options.proxyMemoryRequest,
|
||||
ProxyBindTimeout: "1m",
|
||||
SingleNamespace: options.singleNamespace,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -212,9 +217,14 @@ func render(config installConfig, w io.Writer, options *installOptions) error {
|
|||
return InjectYAML(buf, w, ioutil.Discard, injectOptions)
|
||||
}
|
||||
|
||||
func validate(options *installOptions) error {
|
||||
func (options *installOptions) validate() error {
|
||||
if _, err := log.ParseLevel(options.controllerLogLevel); err != nil {
|
||||
return fmt.Errorf("--controller-log-level must be one of: panic, fatal, error, warn, info, debug")
|
||||
}
|
||||
return options.validate()
|
||||
|
||||
if options.proxyAutoInject && options.singleNamespace {
|
||||
return fmt.Errorf("The --proxy-auto-inject and --single-namespace flags cannot both be specified together")
|
||||
}
|
||||
|
||||
return options.proxyConfigOptions.validate()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ func TestRender(t *testing.T) {
|
|||
defaultConfig.UUID = "deaab91a-f4ab-448a-b7d1-c832a2fa0a60"
|
||||
|
||||
// A configuration that shows that all config setting strings are honored
|
||||
// by `render()`.
|
||||
// by `render()`. Note that `SingleNamespace` is tested in a separate
|
||||
// configuration, since it's incompatible with `ProxyAutoInjectEnabled`.
|
||||
metaConfig := installConfig{
|
||||
Namespace: "Namespace",
|
||||
ControllerImage: "ControllerImage",
|
||||
|
|
@ -64,6 +65,33 @@ func TestRender(t *testing.T) {
|
|||
ProxyBindTimeout: "1m",
|
||||
}
|
||||
|
||||
singleNamespaceConfig := installConfig{
|
||||
Namespace: "Namespace",
|
||||
ControllerImage: "ControllerImage",
|
||||
WebImage: "WebImage",
|
||||
PrometheusImage: "PrometheusImage",
|
||||
GrafanaImage: "GrafanaImage",
|
||||
ControllerReplicas: 1,
|
||||
WebReplicas: 2,
|
||||
PrometheusReplicas: 3,
|
||||
ImagePullPolicy: "ImagePullPolicy",
|
||||
UUID: "UUID",
|
||||
CliVersion: "CliVersion",
|
||||
ControllerLogLevel: "ControllerLogLevel",
|
||||
ControllerComponentLabel: "ControllerComponentLabel",
|
||||
CreatedByAnnotation: "CreatedByAnnotation",
|
||||
ProxyAPIPort: 123,
|
||||
EnableTLS: true,
|
||||
TLSTrustAnchorConfigMapName: "TLSTrustAnchorConfigMapName",
|
||||
ProxyContainerName: "ProxyContainerName",
|
||||
TLSTrustAnchorFileName: "TLSTrustAnchorFileName",
|
||||
TLSCertFileName: "TLSCertFileName",
|
||||
TLSPrivateKeyFileName: "TLSPrivateKeyFileName",
|
||||
TLSTrustAnchorVolumeSpecFileName: "TLSTrustAnchorVolumeSpecFileName",
|
||||
TLSIdentityVolumeSpecFileName: "TLSIdentityVolumeSpecFileName",
|
||||
SingleNamespace: true,
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
config installConfig
|
||||
controlPlaneNamespace string
|
||||
|
|
@ -71,6 +99,7 @@ func TestRender(t *testing.T) {
|
|||
}{
|
||||
{*defaultConfig, defaultControlPlaneNamespace, "testdata/install_default.golden"},
|
||||
{metaConfig, metaConfig.Namespace, "testdata/install_output.golden"},
|
||||
{singleNamespaceConfig, singleNamespaceConfig.Namespace, "testdata/install_single_namespace_output.golden"},
|
||||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
|
|
@ -93,3 +122,40 @@ func TestRender(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidate(t *testing.T) {
|
||||
t.Run("Accepts the default options as valid", func(t *testing.T) {
|
||||
if err := newInstallOptions().validate(); err != nil {
|
||||
t.Fatalf("Unexpected error: %s", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Rejects invalid log level", func(t *testing.T) {
|
||||
options := newInstallOptions()
|
||||
options.controllerLogLevel = "super"
|
||||
expected := "--controller-log-level must be one of: panic, fatal, error, warn, info, debug"
|
||||
|
||||
err := options.validate()
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error, got nothing")
|
||||
}
|
||||
if err.Error() != expected {
|
||||
t.Fatalf("Expected error string\"%s\", got \"%s\"", expected, err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Rejects single namespace install with auto inject", func(t *testing.T) {
|
||||
options := newInstallOptions()
|
||||
options.proxyAutoInject = true
|
||||
options.singleNamespace = true
|
||||
expected := "The --proxy-auto-inject and --single-namespace flags cannot both be specified together"
|
||||
|
||||
err := options.validate()
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error, got nothing")
|
||||
}
|
||||
if err.Error() != expected {
|
||||
t.Fatalf("Expected error string\"%s\", got \"%s\"", expected, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -142,6 +142,7 @@ spec:
|
|||
- public-api
|
||||
- -prometheus-url=http://prometheus.linkerd.svc.cluster.local:9090
|
||||
- -controller-namespace=linkerd
|
||||
- -single-namespace=false
|
||||
- -log-level=info
|
||||
image: gcr.io/linkerd-io/controller:undefined
|
||||
imagePullPolicy: IfNotPresent
|
||||
|
|
@ -164,6 +165,8 @@ spec:
|
|||
resources: {}
|
||||
- args:
|
||||
- destination
|
||||
- -controller-namespace=linkerd
|
||||
- -single-namespace=false
|
||||
- -enable-tls=false
|
||||
- -log-level=info
|
||||
image: gcr.io/linkerd-io/controller:undefined
|
||||
|
|
@ -210,8 +213,9 @@ spec:
|
|||
resources: {}
|
||||
- args:
|
||||
- tap
|
||||
- -log-level=info
|
||||
- -controller-namespace=linkerd
|
||||
- -single-namespace=false
|
||||
- -log-level=info
|
||||
image: gcr.io/linkerd-io/controller:undefined
|
||||
imagePullPolicy: IfNotPresent
|
||||
livenessProbe:
|
||||
|
|
|
|||
|
|
@ -145,6 +145,7 @@ spec:
|
|||
- public-api
|
||||
- -prometheus-url=http://prometheus.Namespace.svc.cluster.local:9090
|
||||
- -controller-namespace=Namespace
|
||||
- -single-namespace=false
|
||||
- -log-level=ControllerLogLevel
|
||||
image: ControllerImage
|
||||
imagePullPolicy: ImagePullPolicy
|
||||
|
|
@ -167,6 +168,8 @@ spec:
|
|||
resources: {}
|
||||
- args:
|
||||
- destination
|
||||
- -controller-namespace=Namespace
|
||||
- -single-namespace=false
|
||||
- -enable-tls=true
|
||||
- -log-level=ControllerLogLevel
|
||||
image: ControllerImage
|
||||
|
|
@ -213,8 +216,9 @@ spec:
|
|||
resources: {}
|
||||
- args:
|
||||
- tap
|
||||
- -log-level=ControllerLogLevel
|
||||
- -controller-namespace=Namespace
|
||||
- -single-namespace=false
|
||||
- -log-level=ControllerLogLevel
|
||||
image: ControllerImage
|
||||
imagePullPolicy: ImagePullPolicy
|
||||
livenessProbe:
|
||||
|
|
@ -938,6 +942,7 @@ spec:
|
|||
- args:
|
||||
- ca
|
||||
- -controller-namespace=Namespace
|
||||
- -single-namespace=false
|
||||
- -proxy-auto-inject=true
|
||||
- -log-level=ControllerLogLevel
|
||||
image: ControllerImage
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -21,10 +21,13 @@ metadata:
|
|||
|
||||
### Controller RBAC ###
|
||||
---
|
||||
kind: ClusterRole
|
||||
kind: {{if not .SingleNamespace}}Cluster{{end}}Role
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
metadata:
|
||||
name: linkerd-{{.Namespace}}-controller
|
||||
{{- if .SingleNamespace}}
|
||||
namespace: {{.Namespace}}
|
||||
{{- end}}
|
||||
rules:
|
||||
- apiGroups: ["extensions", "apps"]
|
||||
resources: ["deployments", "replicasets"]
|
||||
|
|
@ -34,13 +37,16 @@ rules:
|
|||
verbs: ["list", "get", "watch"]
|
||||
|
||||
---
|
||||
kind: ClusterRoleBinding
|
||||
kind: {{if not .SingleNamespace}}Cluster{{end}}RoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
metadata:
|
||||
name: linkerd-{{.Namespace}}-controller
|
||||
{{- if .SingleNamespace}}
|
||||
namespace: {{.Namespace}}
|
||||
{{- end}}
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
kind: {{if not .SingleNamespace}}Cluster{{end}}Role
|
||||
name: linkerd-{{.Namespace}}-controller
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
|
|
@ -57,23 +63,29 @@ metadata:
|
|||
|
||||
### Prometheus RBAC ###
|
||||
---
|
||||
kind: ClusterRole
|
||||
kind: {{if not .SingleNamespace}}Cluster{{end}}Role
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
metadata:
|
||||
name: linkerd-{{.Namespace}}-prometheus
|
||||
{{- if .SingleNamespace}}
|
||||
namespace: {{.Namespace}}
|
||||
{{- end}}
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["pods"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
|
||||
---
|
||||
kind: ClusterRoleBinding
|
||||
kind: {{if not .SingleNamespace}}Cluster{{end}}RoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
metadata:
|
||||
name: linkerd-{{.Namespace}}-prometheus
|
||||
{{- if .SingleNamespace}}
|
||||
namespace: {{.Namespace}}
|
||||
{{- end}}
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
kind: {{if not .SingleNamespace}}Cluster{{end}}Role
|
||||
name: linkerd-{{.Namespace}}-prometheus
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
|
|
@ -152,6 +164,7 @@ spec:
|
|||
- "public-api"
|
||||
- "-prometheus-url=http://prometheus.{{.Namespace}}.svc.cluster.local:9090"
|
||||
- "-controller-namespace={{.Namespace}}"
|
||||
- "-single-namespace={{.SingleNamespace}}"
|
||||
- "-log-level={{.ControllerLogLevel}}"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
|
|
@ -173,6 +186,8 @@ spec:
|
|||
imagePullPolicy: {{.ImagePullPolicy}}
|
||||
args:
|
||||
- "destination"
|
||||
- "-controller-namespace={{.Namespace}}"
|
||||
- "-single-namespace={{.SingleNamespace}}"
|
||||
- "-enable-tls={{.EnableTLS}}"
|
||||
- "-log-level={{.ControllerLogLevel}}"
|
||||
livenessProbe:
|
||||
|
|
@ -217,8 +232,9 @@ spec:
|
|||
imagePullPolicy: {{.ImagePullPolicy}}
|
||||
args:
|
||||
- "tap"
|
||||
- "-log-level={{.ControllerLogLevel}}"
|
||||
- "-controller-namespace={{.Namespace}}"
|
||||
- "-single-namespace={{.SingleNamespace}}"
|
||||
- "-log-level={{.ControllerLogLevel}}"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /ping
|
||||
|
|
@ -421,6 +437,10 @@ data:
|
|||
- job_name: 'linkerd-proxy'
|
||||
kubernetes_sd_configs:
|
||||
- role: pod
|
||||
{{- if .SingleNamespace}}
|
||||
namespaces:
|
||||
names: ['{{.Namespace}}']
|
||||
{{- end}}
|
||||
relabel_configs:
|
||||
- source_labels:
|
||||
- __meta_kubernetes_pod_container_name
|
||||
|
|
@ -596,10 +616,13 @@ metadata:
|
|||
|
||||
### CA RBAC ###
|
||||
---
|
||||
kind: ClusterRole
|
||||
kind: {{if not .SingleNamespace}}Cluster{{end}}Role
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
metadata:
|
||||
name: linkerd-{{.Namespace}}-ca
|
||||
{{- if .SingleNamespace}}
|
||||
namespace: {{.Namespace}}
|
||||
{{- end}}
|
||||
rules:
|
||||
- apiGroups: [""]
|
||||
resources: ["configmaps"]
|
||||
|
|
@ -624,13 +647,16 @@ rules:
|
|||
{{- end }}
|
||||
|
||||
---
|
||||
kind: ClusterRoleBinding
|
||||
kind: {{if not .SingleNamespace}}Cluster{{end}}RoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1beta1
|
||||
metadata:
|
||||
name: linkerd-{{.Namespace}}-ca
|
||||
{{- if .SingleNamespace}}
|
||||
namespace: {{.Namespace}}
|
||||
{{- end}}
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: ClusterRole
|
||||
kind: {{if not .SingleNamespace}}Cluster{{end}}Role
|
||||
name: linkerd-{{.Namespace}}-ca
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
|
|
@ -668,6 +694,7 @@ spec:
|
|||
args:
|
||||
- "ca"
|
||||
- "-controller-namespace={{.Namespace}}"
|
||||
- "-single-namespace={{.SingleNamespace}}"
|
||||
{{- if and .EnableTLS .ProxyAutoInjectEnabled }}
|
||||
- "-proxy-auto-inject={{ .ProxyAutoInjectEnabled }}"
|
||||
{{- end }}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import (
|
|||
func main() {
|
||||
metricsAddr := flag.String("metrics-addr", ":9997", "address to serve scrapable metrics on")
|
||||
controllerNamespace := flag.String("controller-namespace", "linkerd", "namespace in which Linkerd is installed")
|
||||
singleNamespace := flag.Bool("single-namespace", false, "only operate in the controller namespace")
|
||||
kubeConfigPath := flag.String("kubeconfig", "", "path to kube config")
|
||||
proxyAutoInject := flag.Bool("proxy-auto-inject", false, "if true, watch for the add and update events of mutating webhook configurations")
|
||||
flags.ConfigureAndParse()
|
||||
|
|
@ -28,11 +29,16 @@ func main() {
|
|||
log.Fatal(err.Error())
|
||||
}
|
||||
|
||||
restrictToNamespace := ""
|
||||
if *singleNamespace {
|
||||
restrictToNamespace = *controllerNamespace
|
||||
}
|
||||
|
||||
var k8sAPI *k8s.API
|
||||
if *proxyAutoInject {
|
||||
k8sAPI = k8s.NewAPI(k8sClient, k8s.Pod, k8s.RS, k8s.MWC)
|
||||
k8sAPI = k8s.NewAPI(k8sClient, restrictToNamespace, k8s.Pod, k8s.RS, k8s.MWC)
|
||||
} else {
|
||||
k8sAPI = k8s.NewAPI(k8sClient, k8s.Pod, k8s.RS)
|
||||
k8sAPI = k8s.NewAPI(k8sClient, restrictToNamespace, k8s.Pod, k8s.RS)
|
||||
}
|
||||
|
||||
controller, err := ca.NewCertificateController(*controllerNamespace, k8sAPI, *proxyAutoInject)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ func main() {
|
|||
kubeConfigPath := flag.String("kubeconfig", "", "path to kube config")
|
||||
k8sDNSZone := flag.String("kubernetes-dns-zone", "", "The DNS suffix for the local Kubernetes zone.")
|
||||
enableTLS := flag.Bool("enable-tls", false, "Enable TLS connections among pods in the service mesh")
|
||||
controllerNamespace := flag.String("controller-namespace", "linkerd", "namespace in which Linkerd is installed")
|
||||
singleNamespace := flag.Bool("single-namespace", false, "only operate in the controller namespace")
|
||||
flags.ConfigureAndParse()
|
||||
|
||||
stop := make(chan os.Signal, 1)
|
||||
|
|
@ -28,8 +30,13 @@ func main() {
|
|||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
restrictToNamespace := ""
|
||||
if *singleNamespace {
|
||||
restrictToNamespace = *controllerNamespace
|
||||
}
|
||||
k8sAPI := k8s.NewAPI(
|
||||
k8sClient,
|
||||
restrictToNamespace,
|
||||
k8s.Endpoint,
|
||||
k8s.Pod,
|
||||
k8s.RS,
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ func main() {
|
|||
metricsAddr := flag.String("metrics-addr", ":9995", "address to serve scrapable metrics on")
|
||||
tapAddr := flag.String("tap-addr", "127.0.0.1:8088", "address of tap service")
|
||||
controllerNamespace := flag.String("controller-namespace", "linkerd", "namespace in which Linkerd is installed")
|
||||
singleNamespace := flag.Bool("single-namespace", false, "only operate in the controller namespace")
|
||||
ignoredNamespaces := flag.String("ignore-namespaces", "kube-system", "comma separated list of namespaces to not list pods from")
|
||||
flags.ConfigureAndParse()
|
||||
|
||||
|
|
@ -40,10 +41,14 @@ func main() {
|
|||
if err != nil {
|
||||
log.Fatal(err.Error())
|
||||
}
|
||||
restrictToNamespace := ""
|
||||
if *singleNamespace {
|
||||
restrictToNamespace = *controllerNamespace
|
||||
}
|
||||
k8sAPI := k8s.NewAPI(
|
||||
k8sClient,
|
||||
restrictToNamespace,
|
||||
k8s.Deploy,
|
||||
k8s.NS,
|
||||
k8s.Pod,
|
||||
k8s.RC,
|
||||
k8s.RS,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ func main() {
|
|||
metricsAddr := flag.String("metrics-addr", ":9998", "address to serve scrapable metrics on")
|
||||
kubeConfigPath := flag.String("kubeconfig", "", "path to kube config")
|
||||
controllerNamespace := flag.String("controller-namespace", "linkerd", "namespace in which Linkerd is installed")
|
||||
singleNamespace := flag.Bool("single-namespace", false, "only operate in the controller namespace")
|
||||
tapPort := flag.Uint("tap-port", 4190, "proxy tap port to connect to")
|
||||
flags.ConfigureAndParse()
|
||||
|
||||
|
|
@ -28,10 +29,14 @@ func main() {
|
|||
if err != nil {
|
||||
log.Fatalf("failed to create Kubernetes client: %s", err)
|
||||
}
|
||||
restrictToNamespace := ""
|
||||
if *singleNamespace {
|
||||
restrictToNamespace = *controllerNamespace
|
||||
}
|
||||
k8sAPI := k8s.NewAPI(
|
||||
clientSet,
|
||||
restrictToNamespace,
|
||||
k8s.Deploy,
|
||||
k8s.NS,
|
||||
k8s.Pod,
|
||||
k8s.RC,
|
||||
k8s.Svc,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import (
|
|||
"google.golang.org/grpc/status"
|
||||
appsv1beta2 "k8s.io/api/apps/v1beta2"
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/informers"
|
||||
|
|
@ -28,7 +29,6 @@ const (
|
|||
CM ApiResource = iota
|
||||
Deploy
|
||||
Endpoint
|
||||
NS
|
||||
Pod
|
||||
RC
|
||||
RS
|
||||
|
|
@ -43,7 +43,6 @@ type API struct {
|
|||
cm coreinformers.ConfigMapInformer
|
||||
deploy appinformers.DeploymentInformer
|
||||
endpoint coreinformers.EndpointsInformer
|
||||
ns coreinformers.NamespaceInformer
|
||||
pod coreinformers.PodInformer
|
||||
rc coreinformers.ReplicationControllerInformer
|
||||
rs appinformers.ReplicaSetInformer
|
||||
|
|
@ -52,16 +51,28 @@ type API struct {
|
|||
|
||||
syncChecks []cache.InformerSynced
|
||||
sharedInformers informers.SharedInformerFactory
|
||||
namespace string
|
||||
}
|
||||
|
||||
// NewAPI takes a Kubernetes client and returns an initialized API
|
||||
func NewAPI(k8sClient kubernetes.Interface, resources ...ApiResource) *API {
|
||||
sharedInformers := informers.NewSharedInformerFactory(k8sClient, 10*time.Minute)
|
||||
func NewAPI(k8sClient kubernetes.Interface, namespace string, resources ...ApiResource) *API {
|
||||
var sharedInformers informers.SharedInformerFactory
|
||||
if namespace == "" {
|
||||
sharedInformers = informers.NewSharedInformerFactory(k8sClient, 10*time.Minute)
|
||||
} else {
|
||||
sharedInformers = informers.NewFilteredSharedInformerFactory(
|
||||
k8sClient,
|
||||
10*time.Minute,
|
||||
namespace,
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
api := &API{
|
||||
Client: k8sClient,
|
||||
syncChecks: make([]cache.InformerSynced, 0),
|
||||
sharedInformers: sharedInformers,
|
||||
namespace: namespace,
|
||||
}
|
||||
|
||||
for _, resource := range resources {
|
||||
|
|
@ -75,9 +86,6 @@ func NewAPI(k8sClient kubernetes.Interface, resources ...ApiResource) *API {
|
|||
case Endpoint:
|
||||
api.endpoint = sharedInformers.Core().V1().Endpoints()
|
||||
api.syncChecks = append(api.syncChecks, api.endpoint.Informer().HasSynced)
|
||||
case NS:
|
||||
api.ns = sharedInformers.Core().V1().Namespaces()
|
||||
api.syncChecks = append(api.syncChecks, api.ns.Informer().HasSynced)
|
||||
case Pod:
|
||||
api.pod = sharedInformers.Core().V1().Pods()
|
||||
api.syncChecks = append(api.syncChecks, api.pod.Informer().HasSynced)
|
||||
|
|
@ -119,13 +127,6 @@ func (api *API) Sync(readyCh chan<- struct{}) {
|
|||
}
|
||||
}
|
||||
|
||||
func (api *API) NS() coreinformers.NamespaceInformer {
|
||||
if api.ns == nil {
|
||||
panic("NS informer not configured")
|
||||
}
|
||||
return api.ns
|
||||
}
|
||||
|
||||
func (api *API) Deploy() appinformers.DeploymentInformer {
|
||||
if api.deploy == nil {
|
||||
panic("Deploy informer not configured")
|
||||
|
|
@ -286,20 +287,32 @@ func (api *API) GetPodsFor(obj runtime.Object, includeFailed bool) ([]*apiv1.Pod
|
|||
return allPods, nil
|
||||
}
|
||||
|
||||
// getNamespaces returns the namespace matching the specified name. If no name
|
||||
// is given, it returns all namespaces, unless the API was configured to only
|
||||
// work with a single namespace, in which case it returns that namespace. Note
|
||||
// that namespace reads are not cached.
|
||||
func (api *API) getNamespaces(name string) ([]runtime.Object, error) {
|
||||
var err error
|
||||
var namespaces []*apiv1.Namespace
|
||||
namespaces := make([]*apiv1.Namespace, 0)
|
||||
|
||||
if name == "" {
|
||||
namespaces, err = api.NS().Lister().List(labels.Everything())
|
||||
} else {
|
||||
var namespace *apiv1.Namespace
|
||||
namespace, err = api.NS().Lister().Get(name)
|
||||
namespaces = []*apiv1.Namespace{namespace}
|
||||
if name == "" && api.namespace != "" {
|
||||
name = api.namespace
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if name == "" {
|
||||
namespaceList, err := api.Client.CoreV1().Namespaces().List(metav1.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, item := range namespaceList.Items {
|
||||
ns := item // must create separate var in order to get unique pointers
|
||||
namespaces = append(namespaces, &ns)
|
||||
}
|
||||
} else {
|
||||
namespace, err := api.Client.CoreV1().Namespaces().Get(name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
namespaces = []*apiv1.Namespace{namespace}
|
||||
}
|
||||
|
||||
objects := []runtime.Object{}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,9 @@ import (
|
|||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
func newAPI(resourceConfigs []string, extraConfigs ...string) (*API, []runtime.Object, error) {
|
||||
|
|
@ -132,6 +134,19 @@ metadata:
|
|||
namespace: not-my-ns`,
|
||||
},
|
||||
},
|
||||
getObjectsExpected{
|
||||
err: nil,
|
||||
namespace: "",
|
||||
resType: k8s.Namespace,
|
||||
name: "",
|
||||
k8sResResults: []string{`
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: my-ns`,
|
||||
},
|
||||
k8sResMisc: []string{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, exp := range expectations {
|
||||
|
|
@ -155,6 +170,37 @@ metadata:
|
|||
}
|
||||
})
|
||||
|
||||
t.Run("In single-namespace mode", func(t *testing.T) {
|
||||
t.Run("Returns only the configured namespace", func(t *testing.T) {
|
||||
ns1 := &apiv1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "namespace1",
|
||||
},
|
||||
}
|
||||
ns2 := &apiv1.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "namespace2",
|
||||
},
|
||||
}
|
||||
|
||||
clientSet := fake.NewSimpleClientset(ns1, ns2)
|
||||
api := NewAPI(clientSet, "namespace1")
|
||||
|
||||
namespaces, err := api.GetObjects("", k8s.Namespace, "")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
if len(namespaces) != 1 {
|
||||
t.Fatalf("expected 1 namespace, got %d", len(namespaces))
|
||||
}
|
||||
|
||||
if namespaces[0].(*apiv1.Namespace).Name != "namespace1" {
|
||||
t.Fatalf("expected namespace1, got %v", namespaces[0])
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("If objects are pods", func(t *testing.T) {
|
||||
t.Run("Return running or pending pods", func(t *testing.T) {
|
||||
expectations := []getObjectsExpected{
|
||||
|
|
|
|||
|
|
@ -25,10 +25,10 @@ func NewFakeAPI(configs ...string) (*API, error) {
|
|||
clientSet := fake.NewSimpleClientset(objs...)
|
||||
return NewAPI(
|
||||
clientSet,
|
||||
"",
|
||||
CM,
|
||||
Deploy,
|
||||
Endpoint,
|
||||
NS,
|
||||
Pod,
|
||||
RC,
|
||||
RS,
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ type HealthCheckOptions struct {
|
|||
ShouldCheckKubeVersion bool
|
||||
ShouldCheckControlPlaneVersion bool
|
||||
ShouldCheckDataPlaneVersion bool
|
||||
SingleNamespace bool
|
||||
}
|
||||
|
||||
type HealthChecker struct {
|
||||
|
|
@ -197,21 +198,28 @@ func (hc *HealthChecker) addLinkerdPreInstallChecks() {
|
|||
},
|
||||
})
|
||||
|
||||
roleType := "ClusterRole"
|
||||
roleBindingType := "ClusterRoleBinding"
|
||||
if hc.SingleNamespace {
|
||||
roleType = "Role"
|
||||
roleBindingType = "RoleBinding"
|
||||
}
|
||||
|
||||
hc.checkers = append(hc.checkers, &checker{
|
||||
category: LinkerdPreInstallCategory,
|
||||
description: "can create ClusterRoles",
|
||||
description: fmt.Sprintf("can create %ss", roleType),
|
||||
fatal: true,
|
||||
check: func() error {
|
||||
return hc.checkCanCreate("", "rbac.authorization.k8s.io", "v1beta1", "ClusterRole")
|
||||
return hc.checkCanCreate("", "rbac.authorization.k8s.io", "v1beta1", roleType)
|
||||
},
|
||||
})
|
||||
|
||||
hc.checkers = append(hc.checkers, &checker{
|
||||
category: LinkerdPreInstallCategory,
|
||||
description: "can create ClusterRoleBindings",
|
||||
description: fmt.Sprintf("can create %ss", roleBindingType),
|
||||
fatal: true,
|
||||
check: func() error {
|
||||
return hc.checkCanCreate("", "rbac.authorization.k8s.io", "v1beta1", "ClusterRoleBinding")
|
||||
return hc.checkCanCreate("", "rbac.authorization.k8s.io", "v1beta1", roleBindingType)
|
||||
},
|
||||
})
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue