diff --git a/cli/cmd/check.go b/cli/cmd/check.go index 520b573d5..95f18c4cb 100644 --- a/cli/cmd/check.go +++ b/cli/cmd/check.go @@ -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) diff --git a/cli/cmd/install.go b/cli/cmd/install.go index a38f86f45..269f0df1f 100644 --- a/cli/cmd/install.go +++ b/cli/cmd/install.go @@ -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() } diff --git a/cli/cmd/install_test.go b/cli/cmd/install_test.go index 9c9ead1c5..75fbc8698 100644 --- a/cli/cmd/install_test.go +++ b/cli/cmd/install_test.go @@ -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) + } + }) +} diff --git a/cli/cmd/testdata/install_default.golden b/cli/cmd/testdata/install_default.golden index dd034f137..77ef94e91 100644 --- a/cli/cmd/testdata/install_default.golden +++ b/cli/cmd/testdata/install_default.golden @@ -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: diff --git a/cli/cmd/testdata/install_output.golden b/cli/cmd/testdata/install_output.golden index 537a6e23a..dd768080c 100644 --- a/cli/cmd/testdata/install_output.golden +++ b/cli/cmd/testdata/install_output.golden @@ -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 diff --git a/cli/cmd/testdata/install_single_namespace_output.golden b/cli/cmd/testdata/install_single_namespace_output.golden new file mode 100644 index 000000000..2868b89cf --- /dev/null +++ b/cli/cmd/testdata/install_single_namespace_output.golden @@ -0,0 +1,1030 @@ +### Namespace ### +kind: Namespace +apiVersion: v1 +metadata: + name: Namespace + +### Service Account Controller ### +--- +kind: ServiceAccount +apiVersion: v1 +metadata: + name: linkerd-controller + namespace: Namespace + +### Controller RBAC ### +--- +kind: Role +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: linkerd-Namespace-controller + namespace: Namespace +rules: +- apiGroups: ["extensions", "apps"] + resources: ["deployments", "replicasets"] + verbs: ["list", "get", "watch"] +- apiGroups: [""] + resources: ["pods", "endpoints", "services", "namespaces", "replicationcontrollers"] + verbs: ["list", "get", "watch"] + +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: linkerd-Namespace-controller + namespace: Namespace +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: linkerd-Namespace-controller +subjects: +- kind: ServiceAccount + name: linkerd-controller + namespace: Namespace + +### Service Account Prometheus ### +--- +kind: ServiceAccount +apiVersion: v1 +metadata: + name: linkerd-prometheus + namespace: Namespace + +### Prometheus RBAC ### +--- +kind: Role +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: linkerd-Namespace-prometheus + namespace: Namespace +rules: +- apiGroups: [""] + resources: ["pods"] + verbs: ["get", "list", "watch"] + +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: linkerd-Namespace-prometheus + namespace: Namespace +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: linkerd-Namespace-prometheus +subjects: +- kind: ServiceAccount + name: linkerd-prometheus + namespace: Namespace + +### Controller ### +--- +kind: Service +apiVersion: v1 +metadata: + name: api + namespace: Namespace + labels: + ControllerComponentLabel: controller + annotations: + CreatedByAnnotation: CliVersion +spec: + type: ClusterIP + selector: + ControllerComponentLabel: controller + ports: + - name: http + port: 8085 + targetPort: 8085 + +--- +kind: Service +apiVersion: v1 +metadata: + name: proxy-api + namespace: Namespace + labels: + ControllerComponentLabel: controller + annotations: + CreatedByAnnotation: CliVersion +spec: + type: ClusterIP + selector: + ControllerComponentLabel: controller + ports: + - name: grpc + port: 123 + targetPort: 123 + +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + annotations: + CreatedByAnnotation: CliVersion + creationTimestamp: null + labels: + ControllerComponentLabel: controller + name: controller + namespace: Namespace +spec: + replicas: 1 + strategy: {} + template: + metadata: + annotations: + CreatedByAnnotation: CliVersion + linkerd.io/created-by: linkerd/cli undefined + linkerd.io/proxy-version: undefined + creationTimestamp: null + labels: + ControllerComponentLabel: controller + linkerd.io/control-plane-ns: Namespace + linkerd.io/proxy-deployment: controller + spec: + containers: + - args: + - public-api + - -prometheus-url=http://prometheus.Namespace.svc.cluster.local:9090 + - -controller-namespace=Namespace + - -single-namespace=true + - -log-level=ControllerLogLevel + image: ControllerImage + imagePullPolicy: ImagePullPolicy + livenessProbe: + httpGet: + path: /ping + port: 9995 + initialDelaySeconds: 10 + name: public-api + ports: + - containerPort: 8085 + name: http + - containerPort: 9995 + name: admin-http + readinessProbe: + failureThreshold: 7 + httpGet: + path: /ready + port: 9995 + resources: {} + - args: + - destination + - -controller-namespace=Namespace + - -single-namespace=true + - -enable-tls=true + - -log-level=ControllerLogLevel + image: ControllerImage + imagePullPolicy: ImagePullPolicy + livenessProbe: + httpGet: + path: /ping + port: 9999 + initialDelaySeconds: 10 + name: destination + ports: + - containerPort: 8089 + name: grpc + - containerPort: 9999 + name: admin-http + readinessProbe: + failureThreshold: 7 + httpGet: + path: /ready + port: 9999 + resources: {} + - args: + - proxy-api + - -addr=:123 + - -log-level=ControllerLogLevel + image: ControllerImage + imagePullPolicy: ImagePullPolicy + livenessProbe: + httpGet: + path: /ping + port: 9996 + initialDelaySeconds: 10 + name: proxy-api + ports: + - containerPort: 123 + name: grpc + - containerPort: 9996 + name: admin-http + readinessProbe: + failureThreshold: 7 + httpGet: + path: /ready + port: 9996 + resources: {} + - args: + - tap + - -controller-namespace=Namespace + - -single-namespace=true + - -log-level=ControllerLogLevel + image: ControllerImage + imagePullPolicy: ImagePullPolicy + livenessProbe: + httpGet: + path: /ping + port: 9998 + initialDelaySeconds: 10 + name: tap + ports: + - containerPort: 8088 + name: grpc + - containerPort: 9998 + name: admin-http + readinessProbe: + failureThreshold: 7 + httpGet: + path: /ready + port: 9998 + resources: {} + - env: + - name: LINKERD2_PROXY_LOG + value: warn,linkerd2_proxy=info + - name: LINKERD2_PROXY_BIND_TIMEOUT + value: 10s + - name: LINKERD2_PROXY_CONTROL_URL + value: tcp://localhost.:8086 + - name: LINKERD2_PROXY_CONTROL_LISTENER + value: tcp://0.0.0.0:4190 + - name: LINKERD2_PROXY_METRICS_LISTENER + value: tcp://0.0.0.0:4191 + - name: LINKERD2_PROXY_OUTBOUND_LISTENER + value: tcp://127.0.0.1:4140 + - name: LINKERD2_PROXY_INBOUND_LISTENER + value: tcp://0.0.0.0:4143 + - name: LINKERD2_PROXY_POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + image: gcr.io/linkerd-io/proxy:undefined + imagePullPolicy: IfNotPresent + livenessProbe: + httpGet: + path: /metrics + port: 4191 + initialDelaySeconds: 10 + name: linkerd-proxy + ports: + - containerPort: 4143 + name: linkerd-proxy + - containerPort: 4191 + name: linkerd-metrics + readinessProbe: + httpGet: + path: /metrics + port: 4191 + initialDelaySeconds: 10 + resources: {} + securityContext: + runAsUser: 2102 + terminationMessagePolicy: FallbackToLogsOnError + initContainers: + - args: + - --incoming-proxy-port + - "4143" + - --outgoing-proxy-port + - "4140" + - --proxy-uid + - "2102" + - --inbound-ports-to-ignore + - 4190,4191 + image: gcr.io/linkerd-io/proxy-init:undefined + imagePullPolicy: IfNotPresent + name: linkerd-init + resources: {} + securityContext: + capabilities: + add: + - NET_ADMIN + privileged: false + terminationMessagePolicy: FallbackToLogsOnError + serviceAccount: linkerd-controller +status: {} +--- +kind: Service +apiVersion: v1 +metadata: + name: web + namespace: Namespace + labels: + ControllerComponentLabel: web + annotations: + CreatedByAnnotation: CliVersion +spec: + type: ClusterIP + selector: + ControllerComponentLabel: web + ports: + - name: http + port: 8084 + targetPort: 8084 + - name: admin-http + port: 9994 + targetPort: 9994 + +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + annotations: + CreatedByAnnotation: CliVersion + creationTimestamp: null + labels: + ControllerComponentLabel: web + name: web + namespace: Namespace +spec: + replicas: 2 + strategy: {} + template: + metadata: + annotations: + CreatedByAnnotation: CliVersion + linkerd.io/created-by: linkerd/cli undefined + linkerd.io/proxy-version: undefined + creationTimestamp: null + labels: + ControllerComponentLabel: web + linkerd.io/control-plane-ns: Namespace + linkerd.io/proxy-deployment: web + spec: + containers: + - args: + - -api-addr=api.Namespace.svc.cluster.local:8085 + - -static-dir=/dist + - -template-dir=/templates + - -uuid=UUID + - -controller-namespace=Namespace + - -log-level=ControllerLogLevel + image: WebImage + imagePullPolicy: ImagePullPolicy + livenessProbe: + httpGet: + path: /ping + port: 9994 + initialDelaySeconds: 10 + name: web + ports: + - containerPort: 8084 + name: http + - containerPort: 9994 + name: admin-http + readinessProbe: + failureThreshold: 7 + httpGet: + path: /ready + port: 9994 + resources: {} + - env: + - name: LINKERD2_PROXY_LOG + value: warn,linkerd2_proxy=info + - name: LINKERD2_PROXY_BIND_TIMEOUT + value: 10s + - name: LINKERD2_PROXY_CONTROL_URL + value: tcp://proxy-api.Namespace.svc.cluster.local:8086 + - name: LINKERD2_PROXY_CONTROL_LISTENER + value: tcp://0.0.0.0:4190 + - name: LINKERD2_PROXY_METRICS_LISTENER + value: tcp://0.0.0.0:4191 + - name: LINKERD2_PROXY_OUTBOUND_LISTENER + value: tcp://127.0.0.1:4140 + - name: LINKERD2_PROXY_INBOUND_LISTENER + value: tcp://0.0.0.0:4143 + - name: LINKERD2_PROXY_POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + image: gcr.io/linkerd-io/proxy:undefined + imagePullPolicy: IfNotPresent + livenessProbe: + httpGet: + path: /metrics + port: 4191 + initialDelaySeconds: 10 + name: linkerd-proxy + ports: + - containerPort: 4143 + name: linkerd-proxy + - containerPort: 4191 + name: linkerd-metrics + readinessProbe: + httpGet: + path: /metrics + port: 4191 + initialDelaySeconds: 10 + resources: {} + securityContext: + runAsUser: 2102 + terminationMessagePolicy: FallbackToLogsOnError + initContainers: + - args: + - --incoming-proxy-port + - "4143" + - --outgoing-proxy-port + - "4140" + - --proxy-uid + - "2102" + - --inbound-ports-to-ignore + - 4190,4191 + image: gcr.io/linkerd-io/proxy-init:undefined + imagePullPolicy: IfNotPresent + name: linkerd-init + resources: {} + securityContext: + capabilities: + add: + - NET_ADMIN + privileged: false + terminationMessagePolicy: FallbackToLogsOnError +status: {} +--- +kind: Service +apiVersion: v1 +metadata: + name: prometheus + namespace: Namespace + labels: + ControllerComponentLabel: prometheus + annotations: + CreatedByAnnotation: CliVersion +spec: + type: ClusterIP + selector: + ControllerComponentLabel: prometheus + ports: + - name: admin-http + port: 9090 + targetPort: 9090 + +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + annotations: + CreatedByAnnotation: CliVersion + creationTimestamp: null + labels: + ControllerComponentLabel: prometheus + name: prometheus + namespace: Namespace +spec: + replicas: 3 + strategy: {} + template: + metadata: + annotations: + CreatedByAnnotation: CliVersion + linkerd.io/created-by: linkerd/cli undefined + linkerd.io/proxy-version: undefined + creationTimestamp: null + labels: + ControllerComponentLabel: prometheus + linkerd.io/control-plane-ns: Namespace + linkerd.io/proxy-deployment: prometheus + spec: + containers: + - args: + - --storage.tsdb.retention=6h + - --config.file=/etc/prometheus/prometheus.yml + image: PrometheusImage + imagePullPolicy: ImagePullPolicy + livenessProbe: + httpGet: + path: /-/healthy + port: 9090 + initialDelaySeconds: 30 + timeoutSeconds: 30 + name: prometheus + ports: + - containerPort: 9090 + name: admin-http + readinessProbe: + httpGet: + path: /-/ready + port: 9090 + initialDelaySeconds: 30 + timeoutSeconds: 30 + resources: {} + volumeMounts: + - mountPath: /etc/prometheus + name: prometheus-config + readOnly: true + - env: + - name: LINKERD2_PROXY_LOG + value: warn,linkerd2_proxy=info + - name: LINKERD2_PROXY_BIND_TIMEOUT + value: 10s + - name: LINKERD2_PROXY_CONTROL_URL + value: tcp://proxy-api.Namespace.svc.cluster.local:8086 + - name: LINKERD2_PROXY_CONTROL_LISTENER + value: tcp://0.0.0.0:4190 + - name: LINKERD2_PROXY_METRICS_LISTENER + value: tcp://0.0.0.0:4191 + - name: LINKERD2_PROXY_OUTBOUND_LISTENER + value: tcp://127.0.0.1:4140 + - name: LINKERD2_PROXY_INBOUND_LISTENER + value: tcp://0.0.0.0:4143 + - name: LINKERD2_PROXY_POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: LINKERD2_PROXY_OUTBOUND_ROUTER_CAPACITY + value: "10000" + image: gcr.io/linkerd-io/proxy:undefined + imagePullPolicy: IfNotPresent + livenessProbe: + httpGet: + path: /metrics + port: 4191 + initialDelaySeconds: 10 + name: linkerd-proxy + ports: + - containerPort: 4143 + name: linkerd-proxy + - containerPort: 4191 + name: linkerd-metrics + readinessProbe: + httpGet: + path: /metrics + port: 4191 + initialDelaySeconds: 10 + resources: {} + securityContext: + runAsUser: 2102 + terminationMessagePolicy: FallbackToLogsOnError + initContainers: + - args: + - --incoming-proxy-port + - "4143" + - --outgoing-proxy-port + - "4140" + - --proxy-uid + - "2102" + - --inbound-ports-to-ignore + - 4190,4191 + image: gcr.io/linkerd-io/proxy-init:undefined + imagePullPolicy: IfNotPresent + name: linkerd-init + resources: {} + securityContext: + capabilities: + add: + - NET_ADMIN + privileged: false + terminationMessagePolicy: FallbackToLogsOnError + serviceAccount: linkerd-prometheus + volumes: + - configMap: + name: prometheus-config + name: prometheus-config +status: {} +--- +kind: ConfigMap +apiVersion: v1 +metadata: + name: prometheus-config + namespace: Namespace + labels: + ControllerComponentLabel: prometheus + annotations: + CreatedByAnnotation: CliVersion +data: + prometheus.yml: |- + global: + scrape_interval: 10s + scrape_timeout: 10s + evaluation_interval: 10s + + scrape_configs: + - job_name: 'prometheus' + static_configs: + - targets: ['localhost:9090'] + + - job_name: 'grafana' + kubernetes_sd_configs: + - role: pod + namespaces: + names: ['Namespace'] + relabel_configs: + - source_labels: + - __meta_kubernetes_pod_container_name + action: keep + regex: ^grafana$ + + - job_name: 'linkerd-controller' + kubernetes_sd_configs: + - role: pod + namespaces: + names: ['Namespace'] + relabel_configs: + - source_labels: + - __meta_kubernetes_pod_label_linkerd_io_control_plane_component + - __meta_kubernetes_pod_container_port_name + action: keep + regex: (.*);admin-http$ + - source_labels: [__meta_kubernetes_pod_container_name] + action: replace + target_label: component + + - job_name: 'linkerd-proxy' + kubernetes_sd_configs: + - role: pod + namespaces: + names: ['Namespace'] + relabel_configs: + - source_labels: + - __meta_kubernetes_pod_container_name + - __meta_kubernetes_pod_container_port_name + - __meta_kubernetes_pod_label_linkerd_io_control_plane_ns + action: keep + regex: ^ProxyContainerName;linkerd-metrics;Namespace$ + - source_labels: [__meta_kubernetes_namespace] + action: replace + target_label: namespace + - source_labels: [__meta_kubernetes_pod_name] + action: replace + target_label: pod + # special case k8s' "job" label, to not interfere with prometheus' "job" + # label + # __meta_kubernetes_pod_label_linkerd_io_proxy_job=foo => + # k8s_job=foo + - source_labels: [__meta_kubernetes_pod_label_linkerd_io_proxy_job] + action: replace + target_label: k8s_job + # __meta_kubernetes_pod_label_linkerd_io_proxy_deployment=foo => + # deployment=foo + - action: labelmap + regex: __meta_kubernetes_pod_label_linkerd_io_proxy_(.+) + # drop all labels that we just made copies of in the previous labelmap + - action: labeldrop + regex: __meta_kubernetes_pod_label_linkerd_io_proxy_(.+) + # __meta_kubernetes_pod_label_linkerd_io_foo=bar => + # foo=bar + - action: labelmap + regex: __meta_kubernetes_pod_label_linkerd_io_(.+) + +### Grafana ### +--- +kind: Service +apiVersion: v1 +metadata: + name: grafana + namespace: Namespace + labels: + ControllerComponentLabel: grafana + annotations: + CreatedByAnnotation: CliVersion +spec: + type: ClusterIP + selector: + ControllerComponentLabel: grafana + ports: + - name: http + port: 3000 + targetPort: 3000 + +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + annotations: + CreatedByAnnotation: CliVersion + creationTimestamp: null + labels: + ControllerComponentLabel: grafana + name: grafana + namespace: Namespace +spec: + replicas: 1 + strategy: {} + template: + metadata: + annotations: + CreatedByAnnotation: CliVersion + linkerd.io/created-by: linkerd/cli undefined + linkerd.io/proxy-version: undefined + creationTimestamp: null + labels: + ControllerComponentLabel: grafana + linkerd.io/control-plane-ns: Namespace + linkerd.io/proxy-deployment: grafana + spec: + containers: + - image: GrafanaImage + imagePullPolicy: ImagePullPolicy + livenessProbe: + httpGet: + path: /api/health + port: 3000 + name: grafana + ports: + - containerPort: 3000 + name: http + readinessProbe: + failureThreshold: 10 + httpGet: + path: /api/health + port: 3000 + initialDelaySeconds: 30 + periodSeconds: 10 + timeoutSeconds: 30 + resources: {} + volumeMounts: + - mountPath: /etc/grafana + name: grafana-config + readOnly: true + - env: + - name: LINKERD2_PROXY_LOG + value: warn,linkerd2_proxy=info + - name: LINKERD2_PROXY_BIND_TIMEOUT + value: 10s + - name: LINKERD2_PROXY_CONTROL_URL + value: tcp://proxy-api.Namespace.svc.cluster.local:8086 + - name: LINKERD2_PROXY_CONTROL_LISTENER + value: tcp://0.0.0.0:4190 + - name: LINKERD2_PROXY_METRICS_LISTENER + value: tcp://0.0.0.0:4191 + - name: LINKERD2_PROXY_OUTBOUND_LISTENER + value: tcp://127.0.0.1:4140 + - name: LINKERD2_PROXY_INBOUND_LISTENER + value: tcp://0.0.0.0:4143 + - name: LINKERD2_PROXY_POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + image: gcr.io/linkerd-io/proxy:undefined + imagePullPolicy: IfNotPresent + livenessProbe: + httpGet: + path: /metrics + port: 4191 + initialDelaySeconds: 10 + name: linkerd-proxy + ports: + - containerPort: 4143 + name: linkerd-proxy + - containerPort: 4191 + name: linkerd-metrics + readinessProbe: + httpGet: + path: /metrics + port: 4191 + initialDelaySeconds: 10 + resources: {} + securityContext: + runAsUser: 2102 + terminationMessagePolicy: FallbackToLogsOnError + initContainers: + - args: + - --incoming-proxy-port + - "4143" + - --outgoing-proxy-port + - "4140" + - --proxy-uid + - "2102" + - --inbound-ports-to-ignore + - 4190,4191 + image: gcr.io/linkerd-io/proxy-init:undefined + imagePullPolicy: IfNotPresent + name: linkerd-init + resources: {} + securityContext: + capabilities: + add: + - NET_ADMIN + privileged: false + terminationMessagePolicy: FallbackToLogsOnError + volumes: + - configMap: + items: + - key: grafana.ini + path: grafana.ini + - key: datasources.yaml + path: provisioning/datasources/datasources.yaml + - key: dashboards.yaml + path: provisioning/dashboards/dashboards.yaml + name: grafana-config + name: grafana-config +status: {} +--- +kind: ConfigMap +apiVersion: v1 +metadata: + name: grafana-config + namespace: Namespace + labels: + ControllerComponentLabel: grafana + annotations: + CreatedByAnnotation: CliVersion +data: + grafana.ini: |- + instance_name = linkerd-grafana + + [server] + root_url = %(protocol)s://%(domain)s:/api/v1/namespaces/Namespace/services/grafana:http/proxy/ + + [auth] + disable_login_form = true + + [auth.anonymous] + enabled = true + org_role = Editor + + [auth.basic] + enabled = false + + [analytics] + check_for_updates = false + + datasources.yaml: |- + apiVersion: 1 + datasources: + - name: prometheus + type: prometheus + access: proxy + orgId: 1 + url: http://prometheus.Namespace.svc.cluster.local:9090 + isDefault: true + jsonData: + timeInterval: "5s" + version: 1 + editable: true + + dashboards.yaml: |- + apiVersion: 1 + providers: + - name: 'default' + orgId: 1 + folder: '' + type: file + disableDeletion: true + editable: true + options: + path: /var/lib/grafana/dashboards + homeDashboardId: linkerd-top-line + +### Service Account CA ### +--- +kind: ServiceAccount +apiVersion: v1 +metadata: + name: linkerd-ca + namespace: Namespace + +### CA RBAC ### +--- +kind: Role +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: linkerd-Namespace-ca + namespace: Namespace +rules: +- apiGroups: [""] + resources: ["configmaps"] + verbs: ["create"] +- apiGroups: [""] + resources: ["configmaps"] + resourceNames: [TLSTrustAnchorConfigMapName] + verbs: ["update"] +- apiGroups: [""] + resources: ["pods"] + verbs: ["list", "get", "watch"] +- apiGroups: ["extensions", "apps"] + resources: ["replicasets"] + verbs: ["list", "get", "watch"] +- apiGroups: [""] + resources: ["secrets"] + verbs: ["create", "update"] + +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1beta1 +metadata: + name: linkerd-Namespace-ca + namespace: Namespace +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: linkerd-Namespace-ca +subjects: +- kind: ServiceAccount + name: linkerd-ca + namespace: Namespace + +### CA ### +--- +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + annotations: + CreatedByAnnotation: CliVersion + creationTimestamp: null + labels: + ControllerComponentLabel: ca + name: ca + namespace: Namespace +spec: + replicas: 1 + strategy: {} + template: + metadata: + annotations: + CreatedByAnnotation: CliVersion + linkerd.io/created-by: linkerd/cli undefined + linkerd.io/proxy-version: undefined + creationTimestamp: null + labels: + ControllerComponentLabel: ca + linkerd.io/control-plane-ns: Namespace + linkerd.io/proxy-deployment: ca + spec: + containers: + - args: + - ca + - -controller-namespace=Namespace + - -single-namespace=true + - -log-level=ControllerLogLevel + image: ControllerImage + imagePullPolicy: ImagePullPolicy + livenessProbe: + httpGet: + path: /ping + port: 9997 + initialDelaySeconds: 10 + name: ca + ports: + - containerPort: 9997 + name: admin-http + readinessProbe: + failureThreshold: 7 + httpGet: + path: /ready + port: 9997 + resources: {} + - env: + - name: LINKERD2_PROXY_LOG + value: warn,linkerd2_proxy=info + - name: LINKERD2_PROXY_BIND_TIMEOUT + value: 10s + - name: LINKERD2_PROXY_CONTROL_URL + value: tcp://proxy-api.Namespace.svc.cluster.local:8086 + - name: LINKERD2_PROXY_CONTROL_LISTENER + value: tcp://0.0.0.0:4190 + - name: LINKERD2_PROXY_METRICS_LISTENER + value: tcp://0.0.0.0:4191 + - name: LINKERD2_PROXY_OUTBOUND_LISTENER + value: tcp://127.0.0.1:4140 + - name: LINKERD2_PROXY_INBOUND_LISTENER + value: tcp://0.0.0.0:4143 + - name: LINKERD2_PROXY_POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + image: gcr.io/linkerd-io/proxy:undefined + imagePullPolicy: IfNotPresent + livenessProbe: + httpGet: + path: /metrics + port: 4191 + initialDelaySeconds: 10 + name: linkerd-proxy + ports: + - containerPort: 4143 + name: linkerd-proxy + - containerPort: 4191 + name: linkerd-metrics + readinessProbe: + httpGet: + path: /metrics + port: 4191 + initialDelaySeconds: 10 + resources: {} + securityContext: + runAsUser: 2102 + terminationMessagePolicy: FallbackToLogsOnError + initContainers: + - args: + - --incoming-proxy-port + - "4143" + - --outgoing-proxy-port + - "4140" + - --proxy-uid + - "2102" + - --inbound-ports-to-ignore + - 4190,4191 + image: gcr.io/linkerd-io/proxy-init:undefined + imagePullPolicy: IfNotPresent + name: linkerd-init + resources: {} + securityContext: + capabilities: + add: + - NET_ADMIN + privileged: false + terminationMessagePolicy: FallbackToLogsOnError + serviceAccount: linkerd-ca +status: {} +--- diff --git a/cli/install/template.go b/cli/install/template.go index df387ec21..43a57a61a 100644 --- a/cli/install/template.go +++ b/cli/install/template.go @@ -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 }} diff --git a/controller/cmd/ca/main.go b/controller/cmd/ca/main.go index e3fa70a0b..18e68966a 100644 --- a/controller/cmd/ca/main.go +++ b/controller/cmd/ca/main.go @@ -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) diff --git a/controller/cmd/destination/main.go b/controller/cmd/destination/main.go index 4b1ba2b93..4bb2aa05f 100644 --- a/controller/cmd/destination/main.go +++ b/controller/cmd/destination/main.go @@ -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, diff --git a/controller/cmd/public-api/main.go b/controller/cmd/public-api/main.go index 8baf1b74b..092feec97 100644 --- a/controller/cmd/public-api/main.go +++ b/controller/cmd/public-api/main.go @@ -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, diff --git a/controller/cmd/tap/main.go b/controller/cmd/tap/main.go index 83c33e5f5..64690880c 100644 --- a/controller/cmd/tap/main.go +++ b/controller/cmd/tap/main.go @@ -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, diff --git a/controller/k8s/api.go b/controller/k8s/api.go index 0112418b4..99cd3644a 100644 --- a/controller/k8s/api.go +++ b/controller/k8s/api.go @@ -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{} diff --git a/controller/k8s/api_test.go b/controller/k8s/api_test.go index 4b5c46a71..9b7c89952 100644 --- a/controller/k8s/api_test.go +++ b/controller/k8s/api_test.go @@ -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{ diff --git a/controller/k8s/test_helper.go b/controller/k8s/test_helper.go index 528061539..ad5e753c7 100644 --- a/controller/k8s/test_helper.go +++ b/controller/k8s/test_helper.go @@ -25,10 +25,10 @@ func NewFakeAPI(configs ...string) (*API, error) { clientSet := fake.NewSimpleClientset(objs...) return NewAPI( clientSet, + "", CM, Deploy, Endpoint, - NS, Pod, RC, RS, diff --git a/pkg/healthcheck/healthcheck.go b/pkg/healthcheck/healthcheck.go index 9e4776fbf..f7cba5ee4 100644 --- a/pkg/healthcheck/healthcheck.go +++ b/pkg/healthcheck/healthcheck.go @@ -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) }, })