github.com/uber/kraken@v0.1.4/agent/cmd/cmd.go (about)

     1  // Copyright (c) 2016-2019 Uber Technologies, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  package cmd
    15  
    16  import (
    17  	"flag"
    18  	"fmt"
    19  	"net/http"
    20  	"os"
    21  	"time"
    22  
    23  	"github.com/uber/kraken/agent/agentserver"
    24  	"github.com/uber/kraken/build-index/tagclient"
    25  	"github.com/uber/kraken/core"
    26  	"github.com/uber/kraken/lib/dockerdaemon"
    27  	"github.com/uber/kraken/lib/dockerregistry/transfer"
    28  	"github.com/uber/kraken/lib/store"
    29  	"github.com/uber/kraken/lib/torrent/networkevent"
    30  	"github.com/uber/kraken/lib/torrent/scheduler"
    31  	"github.com/uber/kraken/metrics"
    32  	"github.com/uber/kraken/nginx"
    33  	"github.com/uber/kraken/utils/configutil"
    34  	"github.com/uber/kraken/utils/log"
    35  	"github.com/uber/kraken/utils/netutil"
    36  
    37  	"github.com/uber-go/tally"
    38  	"go.uber.org/zap"
    39  )
    40  
    41  // Flags defines agent CLI flags.
    42  type Flags struct {
    43  	PeerIP            string
    44  	PeerPort          int
    45  	AgentServerPort   int
    46  	AgentRegistryPort int
    47  	ConfigFile        string
    48  	Zone              string
    49  	KrakenCluster     string
    50  	SecretsFile       string
    51  }
    52  
    53  // ParseFlags parses agent CLI flags.
    54  func ParseFlags() *Flags {
    55  	var flags Flags
    56  	flag.StringVar(
    57  		&flags.PeerIP, "peer-ip", "", "ip which peer will announce itself as")
    58  	flag.IntVar(
    59  		&flags.PeerPort, "peer-port", 0, "port which peer will announce itself as")
    60  	flag.IntVar(
    61  		&flags.AgentServerPort, "agent-server-port", 0, "port which agent server listens on")
    62  	flag.IntVar(
    63  		&flags.AgentRegistryPort, "agent-registry-port", 0, "port which agent registry listens on")
    64  	flag.StringVar(
    65  		&flags.ConfigFile, "config", "", "configuration file path")
    66  	flag.StringVar(
    67  		&flags.Zone, "zone", "", "zone/datacenter name")
    68  	flag.StringVar(
    69  		&flags.KrakenCluster, "cluster", "", "cluster name (e.g. prod01-zone1)")
    70  	flag.StringVar(
    71  		&flags.SecretsFile, "secrets", "", "path to a secrets YAML file to load into configuration")
    72  	flag.Parse()
    73  	return &flags
    74  }
    75  
    76  type options struct {
    77  	config  *Config
    78  	metrics tally.Scope
    79  	logger  *zap.Logger
    80  }
    81  
    82  // Option defines an optional Run parameter.
    83  type Option func(*options)
    84  
    85  // WithConfig ignores config/secrets flags and directly uses the provided config
    86  // struct.
    87  func WithConfig(c Config) Option {
    88  	return func(o *options) { o.config = &c }
    89  }
    90  
    91  // WithMetrics ignores metrics config and directly uses the provided tally scope.
    92  func WithMetrics(s tally.Scope) Option {
    93  	return func(o *options) { o.metrics = s }
    94  }
    95  
    96  // WithLogger ignores logging config and directly uses the provided logger.
    97  func WithLogger(l *zap.Logger) Option {
    98  	return func(o *options) { o.logger = l }
    99  }
   100  
   101  // Run runs the agent.
   102  func Run(flags *Flags, opts ...Option) {
   103  	if flags.PeerPort == 0 {
   104  		panic("must specify non-zero peer port")
   105  	}
   106  	if flags.AgentServerPort == 0 {
   107  		panic("must specify non-zero agent server port")
   108  	}
   109  	if flags.AgentRegistryPort == 0 {
   110  		panic("must specify non-zero agent registry port")
   111  	}
   112  
   113  	var overrides options
   114  	for _, o := range opts {
   115  		o(&overrides)
   116  	}
   117  
   118  	var config Config
   119  	if overrides.config != nil {
   120  		config = *overrides.config
   121  	} else {
   122  		if err := configutil.Load(flags.ConfigFile, &config); err != nil {
   123  			panic(err)
   124  		}
   125  		if flags.SecretsFile != "" {
   126  			if err := configutil.Load(flags.SecretsFile, &config); err != nil {
   127  				panic(err)
   128  			}
   129  		}
   130  	}
   131  
   132  	if overrides.logger != nil {
   133  		log.SetGlobalLogger(overrides.logger.Sugar())
   134  	} else {
   135  		zlog := log.ConfigureLogger(config.ZapLogging)
   136  		defer zlog.Sync()
   137  	}
   138  
   139  	stats := overrides.metrics
   140  	if stats == nil {
   141  		s, closer, err := metrics.New(config.Metrics, flags.KrakenCluster)
   142  		if err != nil {
   143  			log.Fatalf("Failed to init metrics: %s", err)
   144  		}
   145  		stats = s
   146  		defer closer.Close()
   147  	}
   148  
   149  	go metrics.EmitVersion(stats)
   150  
   151  	if flags.PeerIP == "" {
   152  		localIP, err := netutil.GetLocalIP()
   153  		if err != nil {
   154  			log.Fatalf("Error getting local ip: %s", err)
   155  		}
   156  		flags.PeerIP = localIP
   157  	}
   158  
   159  	pctx, err := core.NewPeerContext(
   160  		config.PeerIDFactory, flags.Zone, flags.KrakenCluster, flags.PeerIP, flags.PeerPort, false)
   161  	if err != nil {
   162  		log.Fatalf("Failed to create peer context: %s", err)
   163  	}
   164  
   165  	cads, err := store.NewCADownloadStore(config.CADownloadStore, stats)
   166  	if err != nil {
   167  		log.Fatalf("Failed to create local store: %s", err)
   168  	}
   169  
   170  	netevents, err := networkevent.NewProducer(config.NetworkEvent)
   171  	if err != nil {
   172  		log.Fatalf("Failed to create network event producer: %s", err)
   173  	}
   174  
   175  	trackers, err := config.Tracker.Build()
   176  	if err != nil {
   177  		log.Fatalf("Error building tracker upstream: %s", err)
   178  	}
   179  	go trackers.Monitor(nil)
   180  
   181  	tls, err := config.TLS.BuildClient()
   182  	if err != nil {
   183  		log.Fatalf("Error building client tls config: %s", err)
   184  	}
   185  
   186  	sched, err := scheduler.NewAgentScheduler(
   187  		config.Scheduler, stats, pctx, cads, netevents, trackers, tls)
   188  	if err != nil {
   189  		log.Fatalf("Error creating scheduler: %s", err)
   190  	}
   191  
   192  	buildIndexes, err := config.BuildIndex.Build()
   193  	if err != nil {
   194  		log.Fatalf("Error building build-index upstream: %s", err)
   195  	}
   196  
   197  	tagClient := tagclient.NewClusterClient(buildIndexes, tls)
   198  
   199  	transferer := transfer.NewReadOnlyTransferer(stats, cads, tagClient, sched)
   200  
   201  	registry, err := config.Registry.Build(config.Registry.ReadOnlyParameters(transferer, cads, stats))
   202  	if err != nil {
   203  		log.Fatalf("Failed to init registry: %s", err)
   204  	}
   205  
   206  	registryAddr := fmt.Sprintf("127.0.0.1:%d", flags.AgentRegistryPort)
   207  	dockerCli, err := dockerdaemon.NewDockerClient(config.DockerDaemon, registryAddr)
   208  	if err != nil {
   209  		log.Fatalf("failed to init docker client for preload: %s", err)
   210  	}
   211  
   212  	agentServer := agentserver.New(
   213  		config.AgentServer, stats, cads, sched, tagClient, dockerCli)
   214  	addr := fmt.Sprintf(":%d", flags.AgentServerPort)
   215  	log.Infof("Starting agent server on %s", addr)
   216  	go func() {
   217  		log.Fatal(http.ListenAndServe(addr, agentServer.Handler()))
   218  	}()
   219  
   220  	log.Info("Starting registry...")
   221  	go func() {
   222  		log.Fatal(registry.ListenAndServe())
   223  	}()
   224  
   225  	go heartbeat(stats)
   226  
   227  	// Wipe log files created by the old nginx process which ran as root.
   228  	// TODO(codyg): Swap these with the v2 log files once they are deleted.
   229  	for _, name := range []string{
   230  		"/var/log/kraken/kraken-agent/nginx-access.log",
   231  		"/var/log/kraken/kraken-agent/nginx-error.log",
   232  	} {
   233  		if err := os.Remove(name); err != nil && !os.IsNotExist(err) {
   234  			log.Warnf("Could not remove old root-owned nginx log: %s", err)
   235  		}
   236  	}
   237  
   238  	log.Fatal(nginx.Run(config.Nginx, map[string]interface{}{
   239  		"allowed_cidrs": config.AllowedCidrs,
   240  		"port":          flags.AgentRegistryPort,
   241  		"registry_server": nginx.GetServer(
   242  			config.Registry.Docker.HTTP.Net, config.Registry.Docker.HTTP.Addr),
   243  		"registry_backup": config.RegistryBackup},
   244  		nginx.WithTLS(config.TLS)))
   245  }
   246  
   247  // heartbeat periodically emits a counter metric which allows us to monitor the
   248  // number of active agents.
   249  func heartbeat(stats tally.Scope) {
   250  	for {
   251  		stats.Counter("heartbeat").Inc(1)
   252  		time.Sleep(10 * time.Second)
   253  	}
   254  }