forked from kiali/kiali
-
Notifications
You must be signed in to change notification settings - Fork 0
/
kiali.go
278 lines (239 loc) · 8.63 KB
/
kiali.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
// Kiali
//
// # Kiali Project, The Console for Istio Service Mesh
//
// NOTE! The Kiali API is not for public use and is not supported for any use outside of the Kiali UI itself.
// The API can and will change from version to version with no guarantee of backwards compatibility.
//
// To generate this API document:
// ```
//
// > alias swagger='docker run --rm -it --user $(id -u):$(id -g) -e GOCACHE=/tmp -e GOPATH=$(go env GOPATH):/go -v $HOME:$HOME -w $(pwd) quay.io/goswagger/swagger'
// > swagger generate spec -o ./swagger.json
// > swagger generate markdown --quiet --spec ./swagger.json --output ./kiali_internal_api.md
//
// ```
//
// Schemes: http, https
// BasePath: /api
// Version: _
//
// Consumes:
// - application/json
//
// Produces:
// - application/json
//
// swagger:meta
package main
import (
"context"
"flag"
"os"
"os/signal"
"strings"
"syscall"
_ "go.uber.org/automaxprocs"
"github.com/kiali/kiali/business"
"github.com/kiali/kiali/config"
"github.com/kiali/kiali/controller"
"github.com/kiali/kiali/grafana"
"github.com/kiali/kiali/istio"
"github.com/kiali/kiali/kubernetes"
"github.com/kiali/kiali/kubernetes/cache"
"github.com/kiali/kiali/log"
"github.com/kiali/kiali/models"
"github.com/kiali/kiali/prometheus"
"github.com/kiali/kiali/prometheus/internalmetrics"
"github.com/kiali/kiali/server"
"github.com/kiali/kiali/tracing"
"github.com/kiali/kiali/util"
)
// Identifies the build. These are set via ldflags during the build (see Makefile).
var (
version = "unknown"
commitHash = "unknown"
goVersion = "unknown"
)
// Command line arguments
var (
argConfigFile = flag.String("config", "", "Path to the YAML configuration file. If not specified, environment variables will be used for configuration.")
)
func init() {
// log everything to stderr so that it can be easily gathered by logs, separate log files are problematic with containers
_ = flag.Set("logtostderr", "true")
}
func main() {
log.InitializeLogger()
util.Clock = util.RealClock{}
// process command line
flag.Parse()
validateFlags()
// log startup information
log.Infof("Kiali: Version: %v, Commit: %v, Go: %v", version, commitHash, goVersion)
log.Debugf("Kiali: Command line: [%v]", strings.Join(os.Args, " "))
// load config file if specified, otherwise, rely on environment variables to configure us
if *argConfigFile != "" {
c, err := config.LoadFromFile(*argConfigFile)
if err != nil {
log.Fatal(err)
}
config.Set(c)
} else {
log.Infof("No configuration file specified. Will rely on environment for configuration.")
config.Set(config.NewConfig())
}
updateConfigWithIstioInfo()
cfg := config.Get()
log.Tracef("Kiali Configuration:\n%s", cfg)
if err := config.Validate(*cfg); err != nil {
log.Fatal(err)
}
// prepare our internal metrics so Prometheus can scrape them
internalmetrics.RegisterInternalMetrics()
// Create the business package dependencies.
clientFactory, err := kubernetes.GetClientFactory()
if err != nil {
log.Fatalf("Failed to create client factory. Err: %s", err)
}
log.Info("Initializing Kiali Cache")
cache, err := cache.NewKialiCache(clientFactory.GetSAClients(), *cfg)
if err != nil {
log.Fatalf("Error initializing Kiali Cache. Details: %s", err)
}
defer cache.Stop()
cache.SetBuildInfo(models.BuildInfo{
CommitHash: commitHash,
ContainerVersion: determineContainerVersion(version),
GoVersion: goVersion,
Version: version,
})
discovery := istio.NewDiscovery(clientFactory.GetSAClients(), cache, cfg)
cpm := business.NewControlPlaneMonitor(cache, clientFactory, *cfg, discovery)
// This context is used for polling and for creating some high level clients like tracing.
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
if cfg.ExternalServices.Istio.IstioAPIEnabled {
cpm.PollIstiodForProxyStatus(ctx)
}
// Create shared prometheus client shared by all prometheus requests in the business layer.
prom, err := prometheus.NewClient()
if err != nil {
log.Fatalf("Error creating Prometheus client: %s", err)
}
// Create shared tracing client shared by all tracing requests in the business layer.
// Because tracing is not an essential component, we don't want to block startup
// of the server if the tracing client fails to initialize. tracing.NewClient will
// continue to retry until the client is initialized or the context is cancelled.
// Passing in a loader function allows the tracing client to be used once it is
// finally initialized.
var tracingClient tracing.ClientInterface
tracingLoader := func() tracing.ClientInterface {
return tracingClient
}
if cfg.ExternalServices.Tracing.Enabled {
go func() {
client, err := tracing.NewClient(ctx, cfg, clientFactory.GetSAHomeClusterClient().GetToken())
if err != nil {
log.Fatalf("Error creating tracing client: %s", err)
return
}
tracingClient = client
}()
} else {
log.Debug("Tracing is disabled")
}
grafana := grafana.NewService(cfg, clientFactory.GetSAHomeClusterClient())
// Start listening to requests
server, err := server.NewServer(cpm, clientFactory, cache, cfg, prom, tracingLoader, discovery)
if err != nil {
log.Fatal(err)
}
server.Start()
// Needs to be started after the server so that the cache is started because the controllers use the cache.
layer, err := business.NewLayerWithSAClients(cfg, cache, prom, tracingClient, cpm, grafana, discovery, clientFactory.GetSAClients())
if err != nil {
log.Fatalf("Error creating business layer: %s", err)
}
if err := controller.Start(ctx, clientFactory, cache, &layer.Validations); err != nil {
log.Fatalf("Error creating validations controller: %s", err)
}
// wait forever, or at least until we are told to exit
waitForTermination()
// Shutdown internal components
log.Info("Shutting down internal components")
server.Stop()
}
func waitForTermination() {
// Channel that is notified when we are done and should exit
// TODO: may want to make this a package variable - other things might want to tell us to exit
doneChan := make(chan bool)
signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
go func() {
for range signalChan {
log.Info("Termination Signal Received")
doneChan <- true
}
}()
<-doneChan
}
func validateFlags() {
if *argConfigFile != "" {
if _, err := os.Stat(*argConfigFile); err != nil {
if os.IsNotExist(err) {
log.Debugf("Configuration file [%v] does not exist.", *argConfigFile)
}
}
}
}
// determineContainerVersion will return the version of the image container.
// It does this by looking at an ENV defined in the Dockerfile when the image is built.
// If the ENV is not defined, the version is assumed the same as the given default value.
func determineContainerVersion(defaultVersion string) string {
v := os.Getenv("KIALI_CONTAINER_VERSION")
if v == "" {
return defaultVersion
}
return v
}
// This is used to update the config with information about istio that
// comes from the environment such as the cluster name.
func updateConfigWithIstioInfo() {
conf := *config.Get()
homeCluster := conf.KubernetesConfig.ClusterName
if homeCluster != "" {
// If the cluster name is already set, we don't need to do anything
return
}
err := func() error {
log.Debug("Cluster name is not set. Attempting to auto-detect the cluster name from the home cluster environment.")
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Need to create a temporary client factory here so that we can create a client
// to auto-detect the istio cluster name from the environment. There's a bit of a
// chicken and egg problem with the client factory because the client factory
// uses the cluster id to keep track of all the clients. But in order to create
// a client to get the cluster id from the environment, you need to create a client factory.
// To get around that we create a temporary client factory here and then set the kiali
// config cluster name. We then create the global client factory later in the business
// package and that global client factory has the cluster id set properly.
cf, err := kubernetes.NewClientFactory(ctx, conf)
if err != nil {
return err
}
// Try to auto-detect the cluster name
homeCluster, err = kubernetes.ClusterNameFromIstiod(conf, cf.GetSAHomeClusterClient())
if err != nil {
return err
}
return nil
}()
if err != nil {
log.Warningf("Cannot resolve local cluster name. Err: %s. Falling back to [%s]", err, config.DefaultClusterID)
homeCluster = config.DefaultClusterID
}
log.Debugf("Auto-detected the istio cluster name to be [%s]. Updating the kiali config", homeCluster)
conf.KubernetesConfig.ClusterName = homeCluster
config.Set(&conf)
}