Add allow and link commands (#4466)

This change adds a `allow` and `link` commands, effectivelly enabling a cluster to have more than one set of credentials that allow it to be mirrored. 

Fx #4461

Signed-off-by: Zahari Dichev <zaharidichev@gmail.com>

Co-authored-by: Alex Leong <alex@buoyant.io>
This commit is contained in:
Zahari Dichev 2020-05-28 00:30:55 +03:00 committed by GitHub
parent d4cdd956f5
commit 7b46682841
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 367 additions and 450 deletions

View File

@ -5,8 +5,7 @@ set -e
setValues() {
sed -i "s/$1/$2/" charts/linkerd2/values.yaml
sed -i "s/$1/$2/" charts/linkerd2-cni/values.yaml
sed -i "s/$1/$2/" charts/linkerd2-service-mirror/values.yaml
sed -i "s/$1/$2/" charts/linkerd2-multicluster-remote-setup/values.yaml
sed -i "s/$1/$2/" charts/linkerd2-multicluster/values.yaml
}
showErr() {
@ -20,8 +19,7 @@ trap 'showErr' ERR
bindir=$( cd "${BASH_SOURCE[0]%/*}" && pwd )
rootdir=$( cd "$bindir"/.. && pwd )
"$bindir"/helm lint "$rootdir"/charts/linkerd2-multicluster-remote-setup
"$bindir"/helm lint "$rootdir"/charts/linkerd2-service-mirror
"$bindir"/helm lint "$rootdir"/charts/linkerd2-multicluster
"$bindir"/helm lint "$rootdir"/charts/partials
"$bindir"/helm dep up "$rootdir"/charts/linkerd2-cni
"$bindir"/helm lint "$rootdir"/charts/linkerd2-cni
@ -51,9 +49,7 @@ if [ "$1" = package ]; then
"$bindir"/helm --version "$version" --app-version "$tag" -d "$rootdir"/target/helm package "$rootdir"/charts/linkerd2
"$bindir"/helm --version "$version" --app-version "$tag" -d "$rootdir"/target/helm package "$rootdir"/charts/linkerd2-cni
# TODO: When ready to publish, uncomment
#"$bindir"/helm --version $version --app-version $tag -d "$rootdir"/target/helm package "$rootdir"/charts/linkerd2-service-mirror
#"$bindir"/helm --version $version --app-version $tag -d "$rootdir"/target/helm package "$rootdir"/charts/linkerd2-multicluster-remote-setup
"$bindir"/helm --version "$version" --app-version "$tag" -d "$rootdir"/target/helm package "$rootdir"/charts/linkerd2-multicluster
mv "$rootdir"/target/helm/index-pre.yaml "$rootdir"/target/helm/index-pre-"$version".yaml
"$bindir"/helm repo index --url "https://helm.linkerd.io/$repo/" --merge "$rootdir"/target/helm/index-pre-"$version".yaml "$rootdir"/target/helm

View File

@ -1,27 +0,0 @@
# Linkerd2-multicluster-remote-setup Helm Chart
Linkerd is a *service mesh*, designed to give platform-wide observability,
reliability, and security without requiring configuration or code changes. This
chart provides a reference cluster gateway implementation, which coupled with
Linkerd and the Service Mirror component can enable multicluster communication
and service discovery
## Configuration
The following table lists the configurable parameters of the
linkerd2-multicluster-remote-setup chart and their default values.
| Parameter | Description | Default |
|--------------------------|-----------------------------------------------------------------------------------------------------------------|------------------------|
|`gatewayName` | The name of the gateway that will be installed | `linkerd-gateway` |
|`namespace` | The namespace in which the gateway and SA will be created |`linkerd-multicluster` |
|`identityTrustDomain` | Trust domain used for identity of the existing linkerd installation |`cluster.local` |
|`incomingPort` | The port on which all the gateway will accept incoming traffic |`4180` |
|`linkerdNamespace` | The namespace of the existing Linkerd installation |`linkerd` |
|`nginxImage` | The Nginx image |`nginx` |
|`nginxImageVersion` | The version of the Nginx image |`1.17` |
|`probePath` | The path that will be used by remote clusters for determining whether the gateway is alive |`/health` |
|`probePeriodSeconds` | The interval (in seconds) between liveness probes |`3` |
|`probePort` | The port used for liveliness probing |`4181` |
|`serviceAccountName` | The name of the service account that will be created and used by remote clusters, attempting to mirror services |`linkerd-service-mirror`|

View File

@ -1,16 +0,0 @@
gatewayName: linkerd-gateway
namespace: linkerd-multicluster
identityTrustDomain: cluster.local
incomingPort: 4180
linkerdNamespace: linkerd
probePath: /health
probePeriodSeconds: 3
probePort: 4181
serviceAccountName: linkerd-service-mirror-remote-access
linkerdVersion: linkerdVersionValue
createdByAnnotation: linkerd.io/created-by
nginxImageVersion: 1.17
nginxImage: nginx
proxyOutboundPort: 4140
localProbePath: /health-local
localProbePort: 8888

View File

@ -3,5 +3,5 @@ appVersion: edge-XX.X.X
description: A helm chart containing the resources to enable mirroring of services on remote clusters
kubeVersion: ">=1.13.0-0"
icon: https://linkerd.io/images/logo-only-200h.png
name: "linkerd2-multicluster-remote-setup"
name: "linkerd2-multicluster"
version: 0.1.0

View File

@ -0,0 +1,39 @@
# Linkerd2-multicluster Helm Chart
Linkerd is a *service mesh*, designed to give platform-wide observability,
reliability, and security without requiring configuration or code changes. This
chart provides the components needed to enable communication between clusters.
## Configuration
The following table lists the configurable parameters of the
linkerd2-multicluster chart and their default values.
| Parameter | Description | Default |
|---------------------------------|---------------------------------------------------------------------------------------------|----------------------------------------------|
|`controllerComponentLabel` | Control plane label. Do not edit |`linkerd.io/control-plane-component` |
|`controllerImage` | Docker image for the Service mirror component (uses the Linkerd controller image) |`gcr.io/linkerd-io/controller` |
|`controllerImageVersion` | Tag for the Service Mirror container Docker image |`latest version` |
|`createdByAnnotation` | Annotation label for the proxy create. Do not edit. |`linkerd.io/created-by` |
|`gateway` | If the gateway component should be installed |`true` |
|`gatewayLocalProbePath` | The path that will be used by the local liveness checks to ensure the gateway is alive |`/health-local` |
|`gatewayLocalProbePort` | The port that will be used by the local liveness checks to ensure the gateway is alive |`8888` |
|`gatewayName` | The name of the gateway that will be installed |`linkerd-gateway` |
|`gatewayNginxImage` | The Nginx image |`nginx` |
|`gatewayNginxImageVersion` | The version of the Nginx image |`1.17` |
|`gatewayPort` | The port on which all the gateway will accept incoming traffic |`4180` |
|`gatewayProbePath` | The path that will be used by remote clusters for determining whether the gateway is alive |`/health` |
|`gatewayProbePort` | The port used for liveliness probing |`4181` |
|`gatewayProbeSeconds` | The interval (in seconds) between liveness probes |`3` |
|`identityTrustDomain` | Trust domain used for identity of the existing linkerd installation |`cluster.local` |
|`linkerdNamespace` | The namespace of the existing Linkerd installation |`linkerd` |
|`linkerdVersion` | Control plane version | latest version |
|`namespace` | Service Mirror component namespace |`linkerd-multicluster` |
|`proxyOutboundPort` | The port on which the proxy accepts outbound traffic |`4140` |
|`remoteAccessServiceAccountName` | The name of the service account used to allow remote clusters to mirror local services |`linkerd-service-mirror-remote-access-default`|
|`remoteMirrorServiceAccount` | If the remote mirror service account should be installed |`true` |
|`serviceMirror` | If the service mirror component should be installed |`true` |
|`logLevel` | Log level for the Multicluster components |`info` |
|`serviceMirrorRetryLimit` | Number of times update from the remote cluster is allowed to be requeued (retried) |`3` |
|`serviceMirrorUID` | User id under which the Service Mirror shall be ran |`2103` |

View File

@ -1,3 +1,4 @@
{{if .Values.gateway -}}
---
apiVersion: v1
kind: ConfigMap
@ -12,21 +13,21 @@ data:
}
stream {
server {
listen {{.Values.incomingPort}};
listen {{.Values.gatewayPort}};
proxy_pass 127.0.0.1:{{.Values.proxyOutboundPort}};
}
}
http {
server {
listen {{.Values.probePort}};
location {{.Values.probePath}} {
listen {{.Values.gatewayProbePort}};
location {{.Values.gatewayProbePath}} {
access_log off;
return 200 "healthy\n";
}
}
server {
listen {{.Values.localProbePort}};
location {{.Values.localProbePath}} {
listen {{.Values.gatewayLocalProbePort}};
location {{.Values.gatewayLocalProbePath}} {
access_log off;
return 200 "healthy\n";
}
@ -55,7 +56,7 @@ spec:
annotations:
{{.Values.createdByAnnotation}}: {{default (printf "linkerd/helm %s" .Values.linkerdVersion) .Values.cliVersion}}
linkerd.io/inject: enabled
config.linkerd.io/proxy-require-identity-inbound-ports: "{{.Values.probePort}},{{.Values.incomingPort}}"
config.linkerd.io/proxy-require-identity-inbound-ports: "{{.Values.gatewayProbePort}},{{.Values.gatewayPort}}"
labels:
app: {{.Values.gatewayName}}
spec:
@ -68,21 +69,21 @@ spec:
readinessProbe:
failureThreshold: 7
httpGet:
path: {{.Values.localProbePath}}
port: {{.Values.localProbePort}}
path: {{.Values.gatewayLocalProbePath}}
port: {{.Values.gatewayLocalProbePort}}
livenessProbe:
httpGet:
path: {{.Values.localProbePath}}
port: {{.Values.localProbePort}}
path: {{.Values.gatewayLocalProbePath}}
port: {{.Values.gatewayLocalProbePort}}
initialDelaySeconds: 10
image: {{.Values.nginxImage}}:{{.Values.nginxImageVersion}}
image: {{.Values.gatewayNginxImage}}:{{.Values.gatewayNginxImageVersion}}
ports:
- name: incoming-port
containerPort: {{.Values.incomingPort}}
containerPort: {{.Values.gatewayPort}}
- name: probe-port
containerPort: {{.Values.probePort}}
containerPort: {{.Values.gatewayProbePort}}
- name: local-probe
containerPort: {{.Values.localProbePort}}
containerPort: {{.Values.gatewayLocalProbePort}}
volumeMounts:
- name: config
mountPath: /etc/nginx
@ -95,18 +96,18 @@ metadata:
namespace: {{.Values.namespace}}
annotations:
mirror.linkerd.io/gateway-identity: {{.Values.gatewayName}}.{{.Values.namespace}}.serviceaccount.identity.{{.Values.linkerdNamespace}}.{{.Values.identityTrustDomain}}
mirror.linkerd.io/probe-port: "{{.Values.probePort}}"
mirror.linkerd.io/probe-period: "{{.Values.probePeriodSeconds}}"
mirror.linkerd.io/probe-path: {{.Values.probePath}}
mirror.linkerd.io/probe-port: "{{.Values.gatewayProbePort}}"
mirror.linkerd.io/probe-period: "{{.Values.gatewayProbeSeconds}}"
mirror.linkerd.io/probe-path: {{.Values.gatewayProbePath}}
mirror.linkerd.io/multicluster-gateway: "true"
{{.Values.createdByAnnotation}}: {{default (printf "linkerd/helm %s" .Values.linkerdVersion) .Values.cliVersion}}
spec:
ports:
- name: incoming-port
port: {{.Values.incomingPort}}
port: {{.Values.gatewayPort}}
protocol: TCP
- name: probe-port
port: {{.Values.probePort}}
port: {{.Values.gatewayProbePort}}
protocol: TCP
selector:
app: {{.Values.gatewayName}}
@ -117,4 +118,4 @@ apiVersion: v1
metadata:
name: {{.Values.gatewayName}}
namespace: {{.Values.namespace}}
{{end -}}

View File

@ -1,8 +1,9 @@
{{if .Values.remoteMirrorServiceAccount -}}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: {{.Values.serviceAccountName}}
name: {{.Values.remoteMirrorServiceAccountName}}
namespace: {{.Values.namespace}}
annotations:
{{.Values.createdByAnnotation}}: {{default (printf "linkerd/helm %s" .Values.linkerdVersion) .Values.cliVersion}}
@ -18,7 +19,7 @@ rules:
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{.Values.serviceAccountName}}
name: {{.Values.remoteMirrorServiceAccountName}}
namespace: {{.Values.namespace}}
annotations:
{{.Values.createdByAnnotation}}: {{default (printf "linkerd/helm %s" .Values.linkerdVersion) .Values.cliVersion}}
@ -26,16 +27,17 @@ metadata:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: {{.Values.serviceAccountName}}
name: {{.Values.remoteMirrorServiceAccountName}}
namespace: {{.Values.namespace}}
annotations:
{{.Values.createdByAnnotation}}: {{default (printf "linkerd/helm %s" .Values.linkerdVersion) .Values.cliVersion}}
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: {{.Values.serviceAccountName}}
name: {{.Values.remoteMirrorServiceAccountName}}
subjects:
- kind: ServiceAccount
name: {{.Values.serviceAccountName}}
name: {{.Values.remoteMirrorServiceAccountName}}
namespace: {{.Values.namespace}}
{{end -}}

View File

@ -1,3 +1,4 @@
{{if .Values.serviceMirror -}}
---
kind: Namespace
apiVersion: v1
@ -92,7 +93,7 @@ spec:
- args:
- service-mirror
- -log-level={{.Values.logLevel}}
- -event-requeue-limit={{.Values.eventRequeueLimit}}
- -event-requeue-limit={{.Values.serviceMirrorRetryLimit}}
- -namespace={{.Values.namespace}}
image: {{.Values.controllerImage}}:{{.Values.controllerImageVersion}}
name: service-mirror
@ -102,3 +103,4 @@ spec:
- containerPort: 9999
name: admin-http
serviceAccountName: linkerd-service-mirror
{{end -}}

View File

@ -0,0 +1,25 @@
controllerComponentLabel: linkerd.io/control-plane-component
controllerImage: gcr.io/linkerd-io/controller
controllerImageVersion: linkerdVersionValue
createdByAnnotation: linkerd.io/created-by
gateway: true
gatewayLocalProbePath: /health-local
gatewayLocalProbePort: 8888
gatewayName: linkerd-gateway
gatewayNginxImage: nginx
gatewayNginxImageVersion: 1.17
gatewayPort: 4180
gatewayProbePath: /health
gatewayProbePort: 4181
gatewayProbeSeconds: 3
identityTrustDomain: cluster.local
linkerdNamespace: linkerd
linkerdVersion: linkerdVersionValue
namespace: linkerd-multicluster
proxyOutboundPort: 4140
serviceMirror: true
logLevel: info
serviceMirrorRetryLimit: 3
serviceMirrorUID: 2103
remoteMirrorServiceAccount: true
remoteMirrorServiceAccountName: linkerd-service-mirror-remote-access-default

View File

@ -1,22 +0,0 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
OWNERS
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*~
# Various IDEs
.project
.idea/
*.tmproj

View File

@ -1,8 +0,0 @@
apiVersion: v1
appVersion: edge-XX.X.X
description: A helm chart containing the resources needed by the Linkerd Service Mirror component.
kubeVersion: ">=1.13.0-0"
icon: https://linkerd.io/images/logo-only-200h.png
name: "linkerd2-service-mirror"
version: 0.1.0

View File

@ -1,22 +0,0 @@
# Linkerd2-service-mirror Helm Chart
Linkerd is a *service mesh*, designed to give platform-wide observability,
reliability, and security without requiring configuration or code changes. The
Linkerd Service Mirror makes it possible to mirror services located on remote
clusters with the purpose of routing traffic to them.
## Configuration
The following table lists the configurable parameters of the
linkerd2-service-mirror chart and their default values.
| Parameter | Description | Default |
|--------------------------------------|-----------------------------------------------------------------------------------|-------------------------------|
|`controllerComponentLabel` | Control plane label. Do not edit | `linkerd.io/control-plane-component`|
|`controllerImage` | Docker image for the Service mirror component (uses the Linkerd controller image) |`gcr.io/linkerd-io/controller`|
|`controllerImageVersion` | Tag for the Service Mirror container Docker image |latest version|
|`namespace` | Service Mirror component namespace |`linkerd-service-mirror`|
|`serviceMirrorUID` | User id under which the Service Mirror shall be ran |`2103`|
|`logLevel` | Log level for the Service Mirror component |`info`|
|`eventRequeueLimit` | Number of times update from the remote cluster is allowed to be requeued (retried)|`3`|

View File

@ -1,7 +0,0 @@
namespace: linkerd-multicluster
serviceMirrorUID: 2103
logLevel: info
eventRequeueLimit: 3
controllerImage: gcr.io/linkerd-io/controller
controllerImageVersion: linkerdVersionValue
controllerComponentLabel: linkerd.io/control-plane-component

View File

@ -1,153 +0,0 @@
package cmd
import (
"errors"
"fmt"
"io"
"os"
"github.com/linkerd/linkerd2/pkg/charts"
"github.com/linkerd/linkerd2/pkg/charts/servicemirror"
"github.com/linkerd/linkerd2/pkg/version"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
kerrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/helm/pkg/chartutil"
"sigs.k8s.io/yaml"
)
type installServiceMirrorOptions struct {
namespace string
controlPlaneVersion string
dockerRegistry string
logLevel string
uid int64
requeueLimit int32
}
const helmServiceMirrorDefaultChartName = "linkerd2-service-mirror"
func newCmdInstallServiceMirror() *cobra.Command {
options, err := newInstallServiceMirrorOptionsWithDefaults()
if err != nil {
fmt.Fprint(os.Stderr, err)
os.Exit(1)
}
cmd := &cobra.Command{
Use: "install-service-mirror [flags]",
Short: "Output Kubernetes configs to install Linkerd Service Mirror",
Long: "Output Kubernetes configs to install Linkerd Service Mirror",
RunE: func(cmd *cobra.Command, args []string) error {
return renderServiceMirror(os.Stdout, options)
},
Hidden: true,
}
cmd.PersistentFlags().StringVarP(&options.controlPlaneVersion, "control-plane-version", "", options.controlPlaneVersion, "(Development) Tag to be used for the control plane component images")
cmd.PersistentFlags().StringVar(&options.dockerRegistry, "registry", options.dockerRegistry, "Docker registry to pull images from")
cmd.PersistentFlags().StringVarP(&options.logLevel, "log-level", "", options.logLevel, "Log level for the Service Mirror Component")
cmd.PersistentFlags().Int64Var(&options.uid, "uid", options.uid, "Run the Service Mirror component under this user ID")
cmd.PersistentFlags().Int32Var(&options.requeueLimit, "event-requeue-limit", options.requeueLimit, "The number of times a failed update from the remote cluster is allowed to be requeued (retried)")
cmd.PersistentFlags().StringVarP(&options.namespace, "namespace", "", options.namespace, "The namespace in which the Service Mirror Component is to be installed")
return cmd
}
func newInstallServiceMirrorOptionsWithDefaults() (*installServiceMirrorOptions, error) {
defaults, err := servicemirror.NewValues()
if err != nil {
return nil, err
}
return &installServiceMirrorOptions{
namespace: defaults.Namespace,
controlPlaneVersion: version.Version,
dockerRegistry: defaultDockerRegistry,
logLevel: defaults.LogLevel,
uid: defaults.ServiceMirrorUID,
requeueLimit: defaults.EventRequeueLimit,
}, nil
}
func (options *installServiceMirrorOptions) buildValues() (*servicemirror.Values, error) {
installValues, err := servicemirror.NewValues()
if err != nil {
return nil, err
}
installValues.Namespace = options.namespace
installValues.LogLevel = options.logLevel
installValues.ControllerImageVersion = options.controlPlaneVersion
installValues.ControllerImage = fmt.Sprintf("%s/controller", options.dockerRegistry)
installValues.ServiceMirrorUID = options.uid
installValues.EventRequeueLimit = options.requeueLimit
return installValues, nil
}
func (options *installServiceMirrorOptions) validate() error {
_, err := getLinkerdConfigMap()
if err != nil {
if kerrors.IsNotFound(err) {
return errors.New("you need Linkerd to be installed in order to install the service mirroring component")
}
return err
}
if !alphaNumDashDot.MatchString(options.controlPlaneVersion) {
return fmt.Errorf("%s is not a valid version", options.controlPlaneVersion)
}
if options.namespace == "" {
return errors.New("you need to specify a namespace")
}
if options.namespace == controlPlaneNamespace {
return errors.New("you need to install the service mirror component in a namespace different than the Linkerd one")
}
if _, err := log.ParseLevel(options.logLevel); err != nil {
return fmt.Errorf("--log-level must be one of: panic, fatal, error, warn, info, debug")
}
return nil
}
func renderServiceMirror(w io.Writer, config *installServiceMirrorOptions) error {
if err := config.validate(); err != nil {
return err
}
values, err := config.buildValues()
if err != nil {
return err
}
// Render raw values and create chart config
rawValues, err := yaml.Marshal(values)
if err != nil {
return err
}
files := []*chartutil.BufferedFile{
{Name: chartutil.ChartfileName},
{Name: "templates/service-mirror.yaml"},
}
chart := &charts.Chart{
Name: helmServiceMirrorDefaultChartName,
Dir: helmServiceMirrorDefaultChartName,
Namespace: controlPlaneNamespace,
RawValues: rawValues,
Files: files,
}
buf, err := chart.RenderNoPartials()
if err != nil {
return err
}
w.Write(buf.Bytes())
w.Write([]byte("---\n"))
return nil
}

View File

@ -10,8 +10,6 @@ import (
"os"
"strings"
"k8s.io/apimachinery/pkg/runtime"
"github.com/linkerd/linkerd2/cli/table"
configPb "github.com/linkerd/linkerd2/controller/gen/config"
pb "github.com/linkerd/linkerd2/controller/gen/public"
@ -21,10 +19,12 @@ import (
"github.com/linkerd/linkerd2/pkg/healthcheck"
"github.com/linkerd/linkerd2/pkg/k8s"
"github.com/linkerd/linkerd2/pkg/version"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
corev1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
yamlDecoder "k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/clientcmd/api"
@ -33,32 +33,40 @@ import (
)
const (
defaultMulticlusterNamespace = "linkerd-multicluster"
helmMulticlusterRemoteSetuprDefaultChartName = "linkerd2-multicluster-remote-setup"
tokenKey = "token"
defaultServiceAccountName = "linkerd-service-mirror-remote-access"
defaultClusterName = "remote"
defaultMulticlusterNamespace = "linkerd-multicluster"
helmMulticlusterDefaultChartName = "linkerd2-multicluster"
tokenKey = "token"
defaultServiceAccountName = "linkerd-service-mirror-remote-access-default"
)
type (
getCredentialsOptions struct {
namespace string
serviceAccountName string
serviceAccountNamespace string
clusterName string
remoteClusterDomain string
}
setupRemoteClusterOptions struct {
allowOptions struct {
namespace string
serviceAccountName string
gatewayName string
probePort uint32
incomingPort uint32
probePeriodSeconds uint32
probePath string
nginxImageVersion string
nginxImage string
ignoreCluster bool
}
multiclusterInstallOptions struct {
gateway bool
gatewayPort uint32
gatewayProbeSeconds uint32
gatewayProbePort uint32
namespace string
serviceMirror bool
serviceMirrorRetryLimit uint32
logLevel string
gatewayNginxImage string
gatewayNginxVersion string
controlPlaneVersion string
dockerRegistry string
remoteMirrorCredentials bool
}
linkOptions struct {
namespace string
clusterName string
apiServerAddress string
serviceAccountName string
}
exportServiceOptions struct {
@ -73,22 +81,26 @@ type (
}
)
func newSetupRemoteClusterOptionsWithDefault() (*setupRemoteClusterOptions, error) {
func newMulticlusterInstallOptionsWithDefault() (*multiclusterInstallOptions, error) {
defaults, err := mccharts.NewValues()
if err != nil {
return nil, err
}
return &setupRemoteClusterOptions{
serviceAccountName: defaults.ServiceAccountName,
namespace: defaults.Namespace,
gatewayName: defaults.GatewayName,
probePort: defaults.ProbePort,
incomingPort: defaults.IncomingPort,
probePeriodSeconds: defaults.ProbePeriodSeconds,
probePath: defaults.ProbePath,
nginxImageVersion: defaults.NginxImageVersion,
nginxImage: defaults.NginxImage,
return &multiclusterInstallOptions{
gateway: defaults.Gateway,
gatewayPort: defaults.GatewayPort,
gatewayProbeSeconds: defaults.GatewayProbeSeconds,
gatewayProbePort: defaults.GatewayProbePort,
namespace: defaults.Namespace,
serviceMirror: defaults.ServiceMirror,
serviceMirrorRetryLimit: defaults.ServiceMirrorRetryLimit,
logLevel: defaults.LogLevel,
gatewayNginxImage: defaults.GatewayNginxImage,
gatewayNginxVersion: defaults.GatewayNginxImageVersion,
controlPlaneVersion: version.Version,
dockerRegistry: defaultDockerRegistry,
remoteMirrorCredentials: true,
}, nil
}
@ -107,42 +119,161 @@ func getLinkerdConfigMap() (*configPb.All, error) {
return global, nil
}
func buildMulticlusterSetupValues(opts *setupRemoteClusterOptions) (*multicluster.Values, error) {
func buildMulticlusterInstallValues(opts *multiclusterInstallOptions) (*multicluster.Values, error) {
global, err := getLinkerdConfigMap()
if err != nil {
if kerrors.IsNotFound(err) {
return nil, errors.New("you need Linkerd to be installed in order to setup a remote cluster")
return nil, errors.New("you need Linkerd to be installed in order to install multicluster addons")
}
return nil, err
}
if !alphaNumDashDot.MatchString(opts.controlPlaneVersion) {
return nil, fmt.Errorf("%s is not a valid version", opts.controlPlaneVersion)
}
if opts.namespace == "" {
return nil, errors.New("you need to specify a namespace")
}
if opts.namespace == controlPlaneNamespace {
return nil, errors.New("you need to setup the multicluster addons in a namespace different than the Linkerd one")
}
if _, err := log.ParseLevel(opts.logLevel); err != nil {
return nil, fmt.Errorf("--log-level must be one of: panic, fatal, error, warn, info, debug")
}
defaults, err := mccharts.NewValues()
if err != nil {
return nil, err
}
if opts.probePort == defaults.LocalProbePort {
return nil, fmt.Errorf("The probe port needs to be different from %d which is the local probe port", defaults.LocalProbePort)
if opts.gatewayProbePort == defaults.GatewayLocalProbePort {
return nil, fmt.Errorf("The probe port needs to be different from %d which is the local probe port", opts.gatewayProbePort)
}
defaults.GatewayName = opts.gatewayName
defaults.Namespace = opts.namespace
defaults.Gateway = opts.gateway
defaults.GatewayPort = opts.gatewayPort
defaults.GatewayProbeSeconds = opts.gatewayProbeSeconds
defaults.GatewayProbePort = opts.gatewayProbePort
defaults.ServiceMirror = opts.serviceMirror
defaults.ServiceMirrorRetryLimit = opts.serviceMirrorRetryLimit
defaults.LogLevel = opts.logLevel
defaults.GatewayNginxImage = opts.gatewayNginxImage
defaults.GatewayNginxImageVersion = opts.gatewayNginxVersion
defaults.IdentityTrustDomain = global.Global.IdentityContext.TrustDomain
defaults.IncomingPort = opts.incomingPort
defaults.LinkerdNamespace = controlPlaneNamespace
defaults.ProbePath = opts.probePath
defaults.ProbePeriodSeconds = opts.probePeriodSeconds
defaults.ProbePort = opts.probePort
defaults.ProxyOutboundPort = global.Proxy.OutboundPort.Port
defaults.ServiceAccountName = opts.serviceAccountName
defaults.NginxImageVersion = opts.nginxImageVersion
defaults.NginxImage = opts.nginxImage
defaults.LinkerdVersion = version.Version
defaults.ControllerImageVersion = opts.controlPlaneVersion
defaults.ControllerImage = fmt.Sprintf("%s/controller", opts.dockerRegistry)
defaults.RemoteMirrorServiceAccount = opts.remoteMirrorCredentials
return defaults, nil
}
func buildMulticlusterAllowValues(opts *allowOptions) (*mccharts.Values, error) {
kubeAPI, err := k8s.NewAPI(kubeconfigPath, kubeContext, impersonate, impersonateGroup, 0)
if err != nil {
return nil, err
}
if opts.namespace == "" {
return nil, errors.New("you need to specify a namespace")
}
if opts.serviceAccountName == "" {
return nil, errors.New("you need to specify a service account name")
}
if opts.namespace == controlPlaneNamespace {
return nil, errors.New("you need to setup the multicluster addons in a namespace different than the Linkerd one")
}
defaults, err := mccharts.NewValues()
if err != nil {
return nil, err
}
defaults.Namespace = opts.namespace
defaults.LinkerdVersion = version.Version
defaults.Gateway = false
defaults.ServiceMirror = false
defaults.RemoteMirrorServiceAccount = true
defaults.RemoteMirrorServiceAccountName = opts.serviceAccountName
if !opts.ignoreCluster {
acc, err := kubeAPI.CoreV1().ServiceAccounts(defaults.Namespace).Get(defaults.RemoteMirrorServiceAccountName, metav1.GetOptions{})
if err == nil && acc != nil {
return nil, fmt.Errorf("Service account with name %s already exists, use --ignore-cluster for force operation", defaults.RemoteMirrorServiceAccountName)
}
if !kerrors.IsNotFound(err) {
return nil, err
}
}
return defaults, nil
}
func newAllowCommand() *cobra.Command {
opts := allowOptions{
namespace: defaultMulticlusterNamespace,
ignoreCluster: false,
}
cmd := &cobra.Command{
Hidden: false,
Use: "allow",
Short: "Outputs credential resources that allow service-mirror controllers to connect to this cluster",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
values, err := buildMulticlusterAllowValues(&opts)
if err != nil {
return err
}
// Render raw values and create chart config
rawValues, err := yaml.Marshal(values)
if err != nil {
return err
}
files := []*chartutil.BufferedFile{
{Name: chartutil.ChartfileName},
{Name: "templates/namespace.yaml"},
{Name: "templates/remote-access-service-mirror-rbac.yaml"},
}
chart := &charts.Chart{
Name: helmMulticlusterDefaultChartName,
Dir: helmMulticlusterDefaultChartName,
Namespace: controlPlaneNamespace,
RawValues: rawValues,
Files: files,
}
buf, err := chart.RenderNoPartials()
if err != nil {
return err
}
stdout.Write(buf.Bytes())
stdout.Write([]byte("---\n"))
return nil
},
}
cmd.Flags().StringVar(&opts.namespace, "namespace", defaultMulticlusterNamespace, "The destination namespace for the service account.")
cmd.Flags().BoolVar(&opts.ignoreCluster, "ignore-cluster", false, "Ignore cluster configuration")
cmd.Flags().StringVar(&opts.serviceAccountName, "service-account-name", "", "The name of the remote access service account")
return cmd
}
func newGatewaysCommand() *cobra.Command {
opts := gatewaysOptions{}
@ -175,24 +306,20 @@ func newGatewaysCommand() *cobra.Command {
return cmd
}
func newSetupRemoteCommand() *cobra.Command {
options, err := newSetupRemoteClusterOptionsWithDefault()
func newMulticlusterInstallCommand() *cobra.Command {
options, err := newMulticlusterInstallOptionsWithDefault()
if err != nil {
fmt.Fprintf(os.Stderr, "%s", err)
os.Exit(1)
}
cmd := &cobra.Command{
Use: "setup-remote",
Short: "Sets up the remote cluster by creating the gateway and necessary credentials",
Args: cobra.NoArgs,
Use: "install",
Short: "Output Kubernetes configs to install the Linkerd multicluster add-on",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
values, err := buildMulticlusterSetupValues(options)
if options.namespace == controlPlaneNamespace {
return errors.New("you need to specify a namespace different than the one in which Linkerd is installed")
}
values, err := buildMulticlusterInstallValues(options)
if err != nil {
return err
@ -208,12 +335,13 @@ func newSetupRemoteCommand() *cobra.Command {
{Name: chartutil.ChartfileName},
{Name: "templates/namespace.yaml"},
{Name: "templates/gateway.yaml"},
{Name: "templates/service-mirror-rbac.yaml"},
{Name: "templates/service-mirror.yaml"},
{Name: "templates/remote-access-service-mirror-rbac.yaml"},
}
chart := &charts.Chart{
Name: helmMulticlusterRemoteSetuprDefaultChartName,
Dir: helmMulticlusterRemoteSetuprDefaultChartName,
Name: helmMulticlusterDefaultChartName,
Dir: helmMulticlusterDefaultChartName,
Namespace: controlPlaneNamespace,
RawValues: rawValues,
Files: files,
@ -229,29 +357,48 @@ func newSetupRemoteCommand() *cobra.Command {
},
}
cmd.Flags().StringVar(&options.gatewayName, "gateway-name", options.gatewayName, "the name of the gateway")
cmd.Flags().StringVar(&options.namespace, "namespace", options.namespace, "the namespace in which the gateway and service account will be installed")
cmd.Flags().Uint32Var(&options.probePort, "probe-port", options.probePort, "the liveness check port of the gateway")
cmd.Flags().Uint32Var(&options.incomingPort, "incoming-port", options.incomingPort, "the port on the gateway used for all incomming traffic")
cmd.Flags().StringVar(&options.probePath, "probe-path", options.probePath, "the path that will be exercised by the liveness checks")
cmd.Flags().Uint32Var(&options.probePeriodSeconds, "probe-period", options.probePeriodSeconds, "the interval at which the gateway will be checked for being alive in seconds")
cmd.Flags().StringVar(&options.serviceAccountName, "service-account-name", options.serviceAccountName, "the name of the service account")
cmd.Flags().StringVar(&options.nginxImageVersion, "nginx-image-version", options.nginxImageVersion, "the version of nginx to be used")
cmd.Flags().StringVar(&options.nginxImage, "nginx-image", options.nginxImage, "the nginx image to be used")
cmd.Flags().StringVar(&options.namespace, "namespace", options.namespace, "The namespace in which the multicluster add-on is to be installed. Must not be the control plane namespace. ")
cmd.Flags().BoolVar(&options.gateway, "gateway", options.gateway, "If the gateway component should be installed")
cmd.Flags().Uint32Var(&options.gatewayPort, "gateway-port", options.gatewayPort, "The port on the gateway used for all incoming traffic")
cmd.Flags().Uint32Var(&options.gatewayProbeSeconds, "gateway-probe-seconds", options.gatewayProbeSeconds, "The interval at which the gateway will be checked for being alive in seconds")
cmd.Flags().Uint32Var(&options.gatewayProbePort, "gateway-probe-port", options.gatewayProbePort, "The liveness check port of the gateway")
cmd.Flags().BoolVar(&options.serviceMirror, "service-mirror", options.serviceMirror, "If the service-mirror component should be installed")
cmd.Flags().Uint32Var(&options.serviceMirrorRetryLimit, "service-mirror-retry-limit", options.serviceMirrorRetryLimit, "The number of times a failed update from the remote cluster is allowed to be retried")
cmd.Flags().StringVar(&options.logLevel, "log-level", options.logLevel, "Log level for the Multicluster components")
cmd.Flags().StringVar(&options.gatewayNginxImage, "gateway-nginx-image", options.gatewayNginxImage, "The nginx image to be used")
cmd.Flags().StringVar(&options.gatewayNginxVersion, "gateway-nginx-image-version", options.gatewayNginxVersion, "The version of nginx to be used")
cmd.Flags().StringVarP(&options.controlPlaneVersion, "control-plane-version", "", options.controlPlaneVersion, "(Development) Tag to be used for the control plane component images")
cmd.Flags().StringVar(&options.dockerRegistry, "registry", options.dockerRegistry, "Docker registry to pull images from")
cmd.Flags().BoolVar(&options.remoteMirrorCredentials, "remote-mirror-credentials", options.remoteMirrorCredentials, "Whether to install the default remote access service account")
// Hide developer focused flags in release builds.
release, err := version.IsReleaseChannel(version.Version)
if err != nil {
log.Errorf("Unable to parse version: %s", version.Version)
}
if release {
cmd.Flags().MarkHidden("control-plane-version")
cmd.Flags().MarkHidden("gateway-nginx-image")
cmd.Flags().MarkHidden("gateway-nginx-image-version")
}
return cmd
}
func newGetCredentialsCommand() *cobra.Command {
opts := getCredentialsOptions{}
func newLinkCommand() *cobra.Command {
opts := linkOptions{}
cmd := &cobra.Command{
Use: "get-credentials",
Short: "Get cluster credentials as a secret",
Args: cobra.NoArgs,
Use: "link",
Short: "Outputs a Kubernetes secret that allows a service mirror component to connect to this cluster",
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
_, err := getLinkerdConfigMap()
if opts.clusterName == "" {
return errors.New("You need to specify cluster name")
}
configMap, err := getLinkerdConfigMap()
if err != nil {
if kerrors.IsNotFound(err) {
return errors.New("you need Linkerd to be installed on a cluster in order to get its credentials")
@ -276,7 +423,7 @@ func newGetCredentialsCommand() *cobra.Command {
return err
}
sa, err := k.CoreV1().ServiceAccounts(opts.serviceAccountNamespace).Get(opts.serviceAccountName, metav1.GetOptions{})
sa, err := k.CoreV1().ServiceAccounts(opts.namespace).Get(opts.serviceAccountName, metav1.GetOptions{})
if err != nil {
return err
}
@ -292,7 +439,7 @@ func newGetCredentialsCommand() *cobra.Command {
return fmt.Errorf("could not find service account token secret for %s", sa.Name)
}
secret, err := k.CoreV1().Secrets(opts.serviceAccountNamespace).Get(secretName, metav1.GetOptions{})
secret, err := k.CoreV1().Secrets(opts.namespace).Get(secretName, metav1.GetOptions{})
if err != nil {
return err
}
@ -319,6 +466,10 @@ func newGetCredentialsCommand() *cobra.Command {
cluster := config.Clusters[context.Cluster]
if opts.apiServerAddress != "" {
cluster.Server = opts.apiServerAddress
}
config.Clusters = map[string]*api.Cluster{
context.Cluster: cluster,
}
@ -336,7 +487,7 @@ func newGetCredentialsCommand() *cobra.Command {
Namespace: opts.namespace,
Annotations: map[string]string{
k8s.RemoteClusterNameLabel: opts.clusterName,
k8s.RemoteClusterDomainAnnotation: opts.remoteClusterDomain,
k8s.RemoteClusterDomainAnnotation: configMap.Global.ClusterDomain,
k8s.RemoteClusterLinkerdNamespaceAnnotation: controlPlaneNamespace,
},
},
@ -355,11 +506,10 @@ func newGetCredentialsCommand() *cobra.Command {
},
}
cmd.Flags().StringVar(&opts.namespace, "namespace", defaultMulticlusterNamespace, "the namespace in which the secret will be created")
cmd.Flags().StringVar(&opts.serviceAccountName, "service-account-name", defaultServiceAccountName, "the name of the service account")
cmd.Flags().StringVar(&opts.serviceAccountNamespace, "service-account-namespace", defaultMulticlusterNamespace, "the namespace in which the svc account resides on the remote cluster")
cmd.Flags().StringVar(&opts.clusterName, "cluster-name", defaultClusterName, "cluster name")
cmd.Flags().StringVar(&opts.remoteClusterDomain, "remote-cluster-domain", defaultClusterDomain, "custom remote cluster domain")
cmd.Flags().StringVar(&opts.namespace, "namespace", defaultMulticlusterNamespace, "The namespace for the service account")
cmd.Flags().StringVar(&opts.clusterName, "cluster-name", "", "Cluster name")
cmd.Flags().StringVar(&opts.apiServerAddress, "api-server-address", "", "The api server address of the target cluster")
cmd.Flags().StringVar(&opts.serviceAccountName, "service-account-name", defaultServiceAccountName, "The name of the service account associated with the credentials")
return cmd
}
@ -597,14 +747,13 @@ func newCmdMulticluster() *cobra.Command {
Long: `Manages the multicluster setup for Linkerd.
This command provides subcommands to manage the multicluster support
functionality of Linkerd. You can use it to deploy credentials to
remote clusters, extract them as well as export remote services to be
available across clusters.`,
Example: ` # Setup remote cluster.
linkerd --context=cluster-a multicluster setup-remote | kubectl --context=cluster-a apply -f -
functionality of Linkerd. You can use it to install the service mirror
components on a cluster, manage credentials and link clusters together.`,
Example: ` # Install multicluster addons.
linkerd --context=cluster-a cluster install | kubectl --context=cluster-a apply -f -
# Extract mirroring cluster credentials from cluster A and install them on cluster B
linkerd --context=cluster-a multicluster get-credentials --cluster-name=remote | kubectl apply --context=cluster-b -f -
linkerd --context=cluster-a cluster link --cluster-name=remote | kubectl apply --context=cluster-b -f -
# Export services from cluster to be available to other clusters
kubectl get svc -o yaml | linkerd export-service - | kubectl apply -f -
@ -615,12 +764,12 @@ available across clusters.`,
# Exporting all the resources inside a folder and its sub-folders.
linkerd export-service <folder> | kubectl apply -f -`,
}
multiclusterCmd.AddCommand(newGetCredentialsCommand())
multiclusterCmd.AddCommand(newSetupRemoteCommand())
multiclusterCmd.AddCommand(newLinkCommand())
multiclusterCmd.AddCommand(newMulticlusterInstallCommand())
multiclusterCmd.AddCommand(newExportServiceCommand())
multiclusterCmd.AddCommand(newGatewaysCommand())
multiclusterCmd.AddCommand(newAllowCommand())
return multiclusterCmd
}

View File

@ -129,7 +129,6 @@ func init() {
RootCmd.AddCommand(newCmdUninject())
RootCmd.AddCommand(newCmdUpgrade())
RootCmd.AddCommand(newCmdVersion())
RootCmd.AddCommand(newCmdInstallServiceMirror())
RootCmd.AddCommand(newCmdMulticluster())
RootCmd.AddCommand(newCmdUninstall())
}

View File

@ -9,27 +9,36 @@ import (
"sigs.k8s.io/yaml"
)
const helmDefaultChartDir = "linkerd2-multicluster-remote-setup"
const helmDefaultChartDir = "linkerd2-multicluster"
// Values contains the top-level elements in the Helm charts
type Values struct {
CliVersion string `json:"cliVersion"`
GatewayName string `json:"gatewayName"`
Namespace string `json:"namespace"`
IdentityTrustDomain string `json:"identityTrustDomain"`
IncomingPort uint32 `json:"incomingPort"`
LinkerdNamespace string `json:"linkerdNamespace"`
ProbePath string `json:"probePath"`
ProbePeriodSeconds uint32 `json:"probePeriodSeconds"`
ProbePort uint32 `json:"probePort"`
ProxyOutboundPort uint32 `json:"proxyOutboundPort"`
ServiceAccountName string `json:"serviceAccountName"`
NginxImageVersion string `json:"nginxImageVersion"`
NginxImage string `json:"nginxImage"`
LinkerdVersion string `json:"linkerdVersion"`
CreatedByAnnotation string `json:"createdByAnnotation"`
LocalProbePath string `json:"localProbePath"`
LocalProbePort uint32 `json:"localProbePort"`
CliVersion string `json:"cliVersion"`
ControllerComponentLabel string `json:"controllerComponentLabel"`
ControllerImage string `json:"controllerImage"`
ControllerImageVersion string `json:"controllerImageVersion"`
CreatedByAnnotation string `json:"createdByAnnotation"`
Gateway bool `json:"gateway"`
GatewayLocalProbePath string `json:"gatewayLocalProbePath"`
GatewayLocalProbePort uint32 `json:"gatewayLocalProbePort"`
GatewayName string `json:"gatewayName"`
GatewayNginxImage string `json:"gatewayNginxImage"`
GatewayNginxImageVersion string `json:"gatewayNginxImageVersion"`
GatewayPort uint32 `json:"gatewayPort"`
GatewayProbePath string `json:"gatewayProbePath"`
GatewayProbePort uint32 `json:"gatewayProbePort"`
GatewayProbeSeconds uint32 `json:"gatewayProbeSeconds"`
IdentityTrustDomain string `json:"identityTrustDomain"`
LinkerdNamespace string `json:"linkerdNamespace"`
LinkerdVersion string `json:"linkerdVersion"`
Namespace string `json:"namespace"`
ProxyOutboundPort uint32 `json:"proxyOutboundPort"`
ServiceMirror bool `json:"serviceMirror"`
LogLevel string `json:"logLevel"`
ServiceMirrorRetryLimit uint32 `json:"serviceMirrorRetryLimit"`
ServiceMirrorUID int64 `json:"serviceMirrorUID"`
RemoteMirrorServiceAccount bool `json:"remoteMirrorServiceAccount"`
RemoteMirrorServiceAccountName string `json:"remoteMirrorServiceAccountName"`
}
// NewValues returns a new instance of the Values type.

View File

@ -1,50 +0,0 @@
package servicemirror
import (
"fmt"
"github.com/linkerd/linkerd2/pkg/charts"
"k8s.io/helm/pkg/chartutil"
"sigs.k8s.io/yaml"
)
const (
helmDefaultServiceMirrorChartDir = "linkerd2-service-mirror"
)
// Values contains the top-level elements in the Helm charts
type Values struct {
Namespace string `json:"namespace"`
ControllerImage string `json:"controllerImage"`
ControllerImageVersion string `json:"controllerImageVersion"`
ControllerComponentLabel string `json:"controllerComponentLabel"`
ServiceMirrorUID int64 `json:"serviceMirrorUID"`
LogLevel string `json:"logLevel"`
EventRequeueLimit int32 `json:"eventRequeueLimit"`
}
// NewValues returns a new instance of the Values type.
func NewValues() (*Values, error) {
chartDir := fmt.Sprintf("%s/", helmDefaultServiceMirrorChartDir)
v, err := readDefaults(chartDir)
if err != nil {
return nil, err
}
return v, nil
}
// readDefaults read all the default variables from the values.yaml file.
// chartDir is the root directory of the Helm chart where values.yaml is.
func readDefaults(chartDir string) (*Values, error) {
file := &chartutil.BufferedFile{
Name: chartutil.ValuesfileName,
}
if err := charts.ReadFile(chartDir, file); err != nil {
return nil, err
}
values := Values{}
if err := yaml.Unmarshal(charts.InsertVersion(file.Data), &values); err != nil {
return nil, err
}
return &values, nil
}