From d773a47dd3d72ef1bf2a6d05a58a10b83ae4d4a6 Mon Sep 17 00:00:00 2001 From: Andrew Seigner Date: Thu, 5 Sep 2019 11:44:03 -0700 Subject: [PATCH] Shrink controller Docker image from 315MB to 38MB (#3378) The controller Docker image included 7 Go binaries (destination, heartbeat, identity, proxy-injector, public-api, sp-validator, tap), each roughly 35MB, with similar dependencies. Change each controller binary into subcommands of a single `controller` binary, decreasing the controller Docker image size from 315MB to 38MB. Signed-off-by: Andrew Seigner --- controller/Dockerfile | 10 +++---- controller/cmd/destination/main.go | 22 ++++++++------ controller/cmd/heartbeat/main.go | 16 +++++++---- controller/cmd/identity/main.go | 19 ++++++++----- controller/cmd/main.go | 41 +++++++++++++++++++++++++++ controller/cmd/proxy-injector/main.go | 7 +++-- controller/cmd/public-api/main.go | 24 +++++++++------- controller/cmd/sp-validator/main.go | 7 +++-- controller/cmd/tap/main.go | 25 +++++++++------- controller/webhook/launcher.go | 13 +++++---- pkg/flags/flags.go | 8 +++--- proxy-identity/main.go | 9 ++++-- web/main.go | 23 ++++++++------- 13 files changed, 150 insertions(+), 74 deletions(-) create mode 100644 controller/cmd/main.go diff --git a/controller/Dockerfile b/controller/Dockerfile index abd6319fd..c9e5d7fe7 100644 --- a/controller/Dockerfile +++ b/controller/Dockerfile @@ -1,4 +1,4 @@ -## compile controller services +## compile controller service FROM gcr.io/linkerd-io/go-deps:c921a98b as golang WORKDIR /linkerd-build COPY controller/gen controller/gen @@ -11,17 +11,17 @@ COPY charts/partials charts/partials # TODO: `go generate` does not honor -mod=readonly RUN go generate -mod=readonly ./pkg/charts/static -# use `install` so that we produce multiple binaries -RUN CGO_ENABLED=0 GOOS=linux go install -tags prod -mod=readonly ./pkg/... -RUN CGO_ENABLED=0 GOOS=linux go install -tags prod -mod=readonly ./controller/cmd/... +RUN CGO_ENABLED=0 GOOS=linux go build -o /out/controller -tags prod -mod=readonly -ldflags "-s -w" ./controller/cmd ## package runtime FROM scratch ENV PATH=$PATH:/go/bin COPY LICENSE /linkerd/LICENSE -COPY --from=golang /go/bin /go/bin +COPY --from=golang /out/controller /go/bin/controller # for heartbeat (https://versioncheck.linkerd.io/version.json) COPY --from=golang /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ ARG LINKERD_VERSION ENV LINKERD_CONTAINER_VERSION_OVERRIDE=${LINKERD_VERSION} + +ENTRYPOINT ["/go/bin/controller"] diff --git a/controller/cmd/destination/main.go b/controller/cmd/destination/main.go index aa61325cd..1c5ba91ad 100644 --- a/controller/cmd/destination/main.go +++ b/controller/cmd/destination/main.go @@ -1,4 +1,4 @@ -package main +package destination import ( "flag" @@ -16,14 +16,18 @@ import ( log "github.com/sirupsen/logrus" ) -func main() { - addr := flag.String("addr", ":8086", "address to serve on") - metricsAddr := flag.String("metrics-addr", ":9996", "address to serve scrapable metrics on") - kubeConfigPath := flag.String("kubeconfig", "", "path to kube config") - enableH2Upgrade := flag.Bool("enable-h2-upgrade", true, "Enable transparently upgraded HTTP2 connections among pods in the service mesh") - disableIdentity := flag.Bool("disable-identity", false, "Disable identity configuration") - controllerNamespace := flag.String("controller-namespace", "linkerd", "namespace in which Linkerd is installed") - flags.ConfigureAndParse() +// Main executes the destination subcommand +func Main(args []string) { + cmd := flag.NewFlagSet("destination", flag.ExitOnError) + + addr := cmd.String("addr", ":8086", "address to serve on") + metricsAddr := cmd.String("metrics-addr", ":9996", "address to serve scrapable metrics on") + kubeConfigPath := cmd.String("kubeconfig", "", "path to kube config") + enableH2Upgrade := cmd.Bool("enable-h2-upgrade", true, "Enable transparently upgraded HTTP2 connections among pods in the service mesh") + disableIdentity := cmd.Bool("disable-identity", false, "Disable identity configuration") + controllerNamespace := cmd.String("controller-namespace", "linkerd", "namespace in which Linkerd is installed") + + flags.ConfigureAndParse(cmd, args) stop := make(chan os.Signal, 1) signal.Notify(stop, os.Interrupt, syscall.SIGTERM) diff --git a/controller/cmd/heartbeat/main.go b/controller/cmd/heartbeat/main.go index cd9b598b4..b74784039 100644 --- a/controller/cmd/heartbeat/main.go +++ b/controller/cmd/heartbeat/main.go @@ -1,4 +1,4 @@ -package main +package heartbeat import ( "flag" @@ -13,11 +13,15 @@ import ( log "github.com/sirupsen/logrus" ) -func main() { - kubeConfigPath := flag.String("kubeconfig", "", "path to kube config") - prometheusURL := flag.String("prometheus-url", "http://127.0.0.1:9090", "prometheus url") - controllerNamespace := flag.String("controller-namespace", "linkerd", "namespace in which Linkerd is installed") - flags.ConfigureAndParse() +// Main executes the heartbeat subcommand +func Main(args []string) { + cmd := flag.NewFlagSet("heartbeat", flag.ExitOnError) + + kubeConfigPath := cmd.String("kubeconfig", "", "path to kube config") + prometheusURL := cmd.String("prometheus-url", "http://127.0.0.1:9090", "prometheus url") + controllerNamespace := cmd.String("controller-namespace", "linkerd", "namespace in which Linkerd is installed") + + flags.ConfigureAndParse(cmd, args) // Gather the following fields: // - version diff --git a/controller/cmd/identity/main.go b/controller/cmd/identity/main.go index 1403f5d1e..f5de5b2ef 100644 --- a/controller/cmd/identity/main.go +++ b/controller/cmd/identity/main.go @@ -1,4 +1,4 @@ -package main +package identity import ( "flag" @@ -25,14 +25,19 @@ import ( // TODO watch trustAnchorsPath for changes // TODO watch issuerPath for changes // TODO restrict servicetoken audiences (and lifetimes) -func main() { - addr := flag.String("addr", ":8080", "address to serve on") - adminAddr := flag.String("admin-addr", ":9990", "address of HTTP admin server") - kubeConfigPath := flag.String("kubeconfig", "", "path to kube config") - issuerPath := flag.String("issuer", + +// Main executes the identity subcommand +func Main(args []string) { + cmd := flag.NewFlagSet("identity", flag.ExitOnError) + + addr := cmd.String("addr", ":8080", "address to serve on") + adminAddr := cmd.String("admin-addr", ":9990", "address of HTTP admin server") + kubeConfigPath := cmd.String("kubeconfig", "", "path to kube config") + issuerPath := cmd.String("issuer", "/var/run/linkerd/identity/issuer", "path to directory containing issuer credentials") - flags.ConfigureAndParse() + + flags.ConfigureAndParse(cmd, args) cfg, err := config.Global(consts.MountPathGlobalConfig) if err != nil { diff --git a/controller/cmd/main.go b/controller/cmd/main.go new file mode 100644 index 000000000..31e31a1b0 --- /dev/null +++ b/controller/cmd/main.go @@ -0,0 +1,41 @@ +package main + +import ( + "fmt" + "os" + + "github.com/linkerd/linkerd2/controller/cmd/destination" + "github.com/linkerd/linkerd2/controller/cmd/heartbeat" + "github.com/linkerd/linkerd2/controller/cmd/identity" + proxyinjector "github.com/linkerd/linkerd2/controller/cmd/proxy-injector" + publicapi "github.com/linkerd/linkerd2/controller/cmd/public-api" + spvalidator "github.com/linkerd/linkerd2/controller/cmd/sp-validator" + "github.com/linkerd/linkerd2/controller/cmd/tap" +) + +func main() { + if len(os.Args) < 2 { + fmt.Println("expected a subcommand") + os.Exit(1) + } + + switch os.Args[1] { + case "destination": + destination.Main(os.Args[2:]) + case "heartbeat": + heartbeat.Main(os.Args[2:]) + case "identity": + identity.Main(os.Args[2:]) + case "proxy-injector": + proxyinjector.Main(os.Args[2:]) + case "public-api": + publicapi.Main(os.Args[2:]) + case "sp-validator": + spvalidator.Main(os.Args[2:]) + case "tap": + tap.Main(os.Args[2:]) + default: + fmt.Printf("unknown subcommand: %s", os.Args[1]) + os.Exit(1) + } +} diff --git a/controller/cmd/proxy-injector/main.go b/controller/cmd/proxy-injector/main.go index 2b879e2c9..2e586ce78 100644 --- a/controller/cmd/proxy-injector/main.go +++ b/controller/cmd/proxy-injector/main.go @@ -1,4 +1,4 @@ -package main +package proxyinjector import ( "github.com/linkerd/linkerd2/controller/k8s" @@ -6,11 +6,14 @@ import ( "github.com/linkerd/linkerd2/controller/webhook" ) -func main() { +// Main executes the proxy-injector subcommand +func Main(args []string) { webhook.Launch( []k8s.APIResource{k8s.NS, k8s.Deploy, k8s.RC, k8s.RS, k8s.Job, k8s.DS, k8s.SS, k8s.Pod}, 9995, injector.Inject, "linkerd-proxy-injector", + "proxy-injector", + args, ) } diff --git a/controller/cmd/public-api/main.go b/controller/cmd/public-api/main.go index 305248fa1..479830ced 100644 --- a/controller/cmd/public-api/main.go +++ b/controller/cmd/public-api/main.go @@ -1,4 +1,4 @@ -package main +package publicapi import ( "context" @@ -20,15 +20,19 @@ import ( log "github.com/sirupsen/logrus" ) -func main() { - addr := flag.String("addr", ":8085", "address to serve on") - kubeConfigPath := flag.String("kubeconfig", "", "path to kube config") - prometheusURL := flag.String("prometheus-url", "http://127.0.0.1:9090", "prometheus url") - metricsAddr := flag.String("metrics-addr", ":9995", "address to serve scrapable metrics on") - destinationAPIAddr := flag.String("destination-addr", "127.0.0.1:8086", "address of destination service") - controllerNamespace := flag.String("controller-namespace", "linkerd", "namespace in which Linkerd is installed") - ignoredNamespaces := flag.String("ignore-namespaces", "kube-system", "comma separated list of namespaces to not list pods from") - flags.ConfigureAndParse() +// Main executes the public-api subcommand +func Main(args []string) { + cmd := flag.NewFlagSet("public-api", flag.ExitOnError) + + addr := cmd.String("addr", ":8085", "address to serve on") + kubeConfigPath := cmd.String("kubeconfig", "", "path to kube config") + prometheusURL := cmd.String("prometheus-url", "http://127.0.0.1:9090", "prometheus url") + metricsAddr := cmd.String("metrics-addr", ":9995", "address to serve scrapable metrics on") + destinationAPIAddr := cmd.String("destination-addr", "127.0.0.1:8086", "address of destination service") + controllerNamespace := cmd.String("controller-namespace", "linkerd", "namespace in which Linkerd is installed") + ignoredNamespaces := cmd.String("ignore-namespaces", "kube-system", "comma separated list of namespaces to not list pods from") + + flags.ConfigureAndParse(cmd, args) stop := make(chan os.Signal, 1) signal.Notify(stop, os.Interrupt, syscall.SIGTERM) diff --git a/controller/cmd/sp-validator/main.go b/controller/cmd/sp-validator/main.go index 247d352ad..4742b3842 100644 --- a/controller/cmd/sp-validator/main.go +++ b/controller/cmd/sp-validator/main.go @@ -1,15 +1,18 @@ -package main +package spvalidator import ( validator "github.com/linkerd/linkerd2/controller/sp-validator" "github.com/linkerd/linkerd2/controller/webhook" ) -func main() { +// Main executes the sp-validator subcommand +func Main(args []string) { webhook.Launch( nil, 9997, validator.AdmitSP, "linkerd-sp-validator", + "sp-validator", + args, ) } diff --git a/controller/cmd/tap/main.go b/controller/cmd/tap/main.go index ae6a104f0..b08ceeeb2 100644 --- a/controller/cmd/tap/main.go +++ b/controller/cmd/tap/main.go @@ -1,4 +1,4 @@ -package main +package tap import ( "context" @@ -17,17 +17,20 @@ import ( log "github.com/sirupsen/logrus" ) -func main() { - apiServerAddr := flag.String("apiserver-addr", ":8089", "address to serve the apiserver on") - 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") - tapPort := flag.Uint("tap-port", 4190, "proxy tap port to connect to") - tlsCertPath := flag.String("tls-cert", pkgK8s.MountPathTLSCrtPEM, "path to TLS Cert PEM") - tlsKeyPath := flag.String("tls-key", pkgK8s.MountPathTLSKeyPEM, "path to TLS Key PEM") - disableCommonNames := flag.Bool("disable-common-names", false, "disable checks for Common Names (for development)") +// Main executes the tap subcommand +func Main(args []string) { + cmd := flag.NewFlagSet("tap", flag.ExitOnError) - flags.ConfigureAndParse() + apiServerAddr := cmd.String("apiserver-addr", ":8089", "address to serve the apiserver on") + metricsAddr := cmd.String("metrics-addr", ":9998", "address to serve scrapable metrics on") + kubeConfigPath := cmd.String("kubeconfig", "", "path to kube config") + controllerNamespace := cmd.String("controller-namespace", "linkerd", "namespace in which Linkerd is installed") + tapPort := cmd.Uint("tap-port", 4190, "proxy tap port to connect to") + tlsCertPath := cmd.String("tls-cert", pkgK8s.MountPathTLSCrtPEM, "path to TLS Cert PEM") + tlsKeyPath := cmd.String("tls-key", pkgK8s.MountPathTLSKeyPEM, "path to TLS Key PEM") + disableCommonNames := cmd.Bool("disable-common-names", false, "disable checks for Common Names (for development)") + + flags.ConfigureAndParse(cmd, args) stop := make(chan os.Signal, 1) signal.Notify(stop, os.Interrupt, syscall.SIGTERM) diff --git a/controller/webhook/launcher.go b/controller/webhook/launcher.go index d3b91063d..a415d2542 100644 --- a/controller/webhook/launcher.go +++ b/controller/webhook/launcher.go @@ -18,11 +18,14 @@ import ( ) // Launch sets up and starts the webhook and metrics servers -func Launch(APIResources []k8s.APIResource, metricsPort uint32, handler handlerFunc, component string) { - metricsAddr := flag.String("metrics-addr", fmt.Sprintf(":%d", metricsPort), "address to serve scrapable metrics on") - addr := flag.String("addr", ":8443", "address to serve on") - kubeconfig := flag.String("kubeconfig", "", "path to kubeconfig") - flags.ConfigureAndParse() +func Launch(APIResources []k8s.APIResource, metricsPort uint32, handler handlerFunc, component, subcommand string, args []string) { + cmd := flag.NewFlagSet(subcommand, flag.ExitOnError) + + metricsAddr := cmd.String("metrics-addr", fmt.Sprintf(":%d", metricsPort), "address to serve scrapable metrics on") + addr := cmd.String("addr", ":8443", "address to serve on") + kubeconfig := cmd.String("kubeconfig", "", "path to kubeconfig") + + flags.ConfigureAndParse(cmd, args) stop := make(chan os.Signal, 1) defer close(stop) diff --git a/pkg/flags/flags.go b/pkg/flags/flags.go index 9370ef6f1..aff1afded 100644 --- a/pkg/flags/flags.go +++ b/pkg/flags/flags.go @@ -13,17 +13,17 @@ import ( // ConfigureAndParse adds flags that are common to all go processes. This // func calls flag.Parse(), so it should be called after all other flags have // been configured. -func ConfigureAndParse() { +func ConfigureAndParse(cmd *flag.FlagSet, args []string) { klog.InitFlags(nil) flag.Set("stderrthreshold", "FATAL") flag.Set("logtostderr", "false") flag.Set("log_file", "/dev/null") flag.Set("v", "0") - logLevel := flag.String("log-level", log.InfoLevel.String(), + logLevel := cmd.String("log-level", log.InfoLevel.String(), "log level, must be one of: panic, fatal, error, warn, info, debug") - printVersion := flag.Bool("version", false, "print version and exit") + printVersion := cmd.Bool("version", false, "print version and exit") - flag.Parse() + cmd.Parse(args) // set log timestamps formatter := &log.TextFormatter{FullTimestamp: true} diff --git a/proxy-identity/main.go b/proxy-identity/main.go index cdc59ca05..b73d36774 100644 --- a/proxy-identity/main.go +++ b/proxy-identity/main.go @@ -23,9 +23,12 @@ const ( ) func main() { - name := flag.String("name", "", "identity name") - dir := flag.String("dir", "", "directory under which credentials are written") - flags.ConfigureAndParse() + cmd := flag.NewFlagSet("public-api", flag.ExitOnError) + + name := cmd.String("name", "", "identity name") + dir := cmd.String("dir", "", "directory under which credentials are written") + + flags.ConfigureAndParse(cmd, os.Args[1:]) if os.Getenv(envDisabled) != "" { log.Debug("Identity disabled.") diff --git a/web/main.go b/web/main.go index 23f338578..2ff4e554e 100644 --- a/web/main.go +++ b/web/main.go @@ -20,16 +20,19 @@ import ( ) func main() { - addr := flag.String("addr", ":8084", "address to serve on") - metricsAddr := flag.String("metrics-addr", ":9994", "address to serve scrapable metrics on") - apiAddr := flag.String("api-addr", "127.0.0.1:8085", "address of the linkerd-controller-api service") - grafanaAddr := flag.String("grafana-addr", "127.0.0.1:3000", "address of the linkerd-grafana service") - templateDir := flag.String("template-dir", "templates", "directory to search for template files") - staticDir := flag.String("static-dir", "app/dist", "directory to search for static files") - reload := flag.Bool("reload", true, "reloading set to true or false") - controllerNamespace := flag.String("controller-namespace", "linkerd", "namespace in which Linkerd is installed") - kubeConfigPath := flag.String("kubeconfig", "", "path to kube config") - flags.ConfigureAndParse() + cmd := flag.NewFlagSet("public-api", flag.ExitOnError) + + addr := cmd.String("addr", ":8084", "address to serve on") + metricsAddr := cmd.String("metrics-addr", ":9994", "address to serve scrapable metrics on") + apiAddr := cmd.String("api-addr", "127.0.0.1:8085", "address of the linkerd-controller-api service") + grafanaAddr := cmd.String("grafana-addr", "127.0.0.1:3000", "address of the linkerd-grafana service") + templateDir := cmd.String("template-dir", "templates", "directory to search for template files") + staticDir := cmd.String("static-dir", "app/dist", "directory to search for static files") + reload := cmd.Bool("reload", true, "reloading set to true or false") + controllerNamespace := cmd.String("controller-namespace", "linkerd", "namespace in which Linkerd is installed") + kubeConfigPath := cmd.String("kubeconfig", "", "path to kube config") + + flags.ConfigureAndParse(cmd, os.Args[1:]) _, _, err := net.SplitHostPort(*apiAddr) // Verify apiAddr is of the form host:port. if err != nil {