Skip to content

Commit 1932867

Browse files
authored
Merge pull request #323 from arangodb/feature/arangodbexporter
ArangoExporter support
2 parents 9187541 + 95c80bd commit 1932867

File tree

12 files changed

+408
-10
lines changed

12 files changed

+408
-10
lines changed

pkg/apis/deployment/v1alpha/deployment_spec.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ type DeploymentSpec struct {
6161
TLS TLSSpec `json:"tls"`
6262
Sync SyncSpec `json:"sync"`
6363
License LicenseSpec `json:"license"`
64+
Metrics MetricsSpec `json:"metrics"`
6465

6566
Single ServerGroupSpec `json:"single"`
6667
Agents ServerGroupSpec `json:"agents"`
@@ -189,6 +190,7 @@ func (s *DeploymentSpec) SetDefaults(deploymentName string) {
189190
s.Coordinators.SetDefaults(ServerGroupCoordinators, s.GetMode().HasCoordinators(), s.GetMode())
190191
s.SyncMasters.SetDefaults(ServerGroupSyncMasters, s.Sync.IsEnabled(), s.GetMode())
191192
s.SyncWorkers.SetDefaults(ServerGroupSyncWorkers, s.Sync.IsEnabled(), s.GetMode())
193+
s.Metrics.SetDefaults(deploymentName+"-exporter-jwt-token", s.Authentication.IsAuthenticated())
192194
s.Chaos.SetDefaults()
193195
s.Bootstrap.SetDefaults(deploymentName)
194196
}
@@ -228,6 +230,7 @@ func (s *DeploymentSpec) SetDefaultsFrom(source DeploymentSpec) {
228230
s.Coordinators.SetDefaultsFrom(source.Coordinators)
229231
s.SyncMasters.SetDefaultsFrom(source.SyncMasters)
230232
s.SyncWorkers.SetDefaultsFrom(source.SyncWorkers)
233+
s.Metrics.SetDefaultsFrom(source.Metrics)
231234
s.Chaos.SetDefaultsFrom(source.Chaos)
232235
s.Bootstrap.SetDefaultsFrom(source.Bootstrap)
233236
}
@@ -283,6 +286,9 @@ func (s *DeploymentSpec) Validate() error {
283286
if err := s.SyncWorkers.Validate(ServerGroupSyncWorkers, s.Sync.IsEnabled(), s.GetMode(), s.GetEnvironment()); err != nil {
284287
return maskAny(err)
285288
}
289+
if err := s.Metrics.Validate(); err != nil {
290+
return maskAny(errors.Wrap(err, "spec.metrics"))
291+
}
286292
if err := s.Chaos.Validate(); err != nil {
287293
return maskAny(errors.Wrap(err, "spec.chaos"))
288294
}
@@ -352,5 +358,8 @@ func (s DeploymentSpec) ResetImmutableFields(target *DeploymentSpec) []string {
352358
if l := s.SyncWorkers.ResetImmutableFields(ServerGroupSyncWorkers, "syncworkers", &target.SyncWorkers); l != nil {
353359
resetFields = append(resetFields, l...)
354360
}
361+
if l := s.Metrics.ResetImmutableFields("metrics", &target.Metrics); l != nil {
362+
resetFields = append(resetFields, l...)
363+
}
355364
return resetFields
356365
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
//
2+
// DISCLAIMER
3+
//
4+
// Copyright 2018 ArangoDB GmbH, Cologne, Germany
5+
//
6+
// Licensed under the Apache License, Version 2.0 (the "License");
7+
// you may not use this file except in compliance with the License.
8+
// You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing, software
13+
// distributed under the License is distributed on an "AS IS" BASIS,
14+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
// See the License for the specific language governing permissions and
16+
// limitations under the License.
17+
//
18+
// Copyright holder is ArangoDB GmbH, Cologne, Germany
19+
//
20+
//
21+
22+
package v1alpha
23+
24+
import (
25+
"github.com/arangodb/kube-arangodb/pkg/util"
26+
"github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
27+
)
28+
29+
// MetricsAuthenticationSpec contains spec for authentication with arangodb
30+
type MetricsAuthenticationSpec struct {
31+
// JWTTokenSecretName contains the name of the JWT kubernetes secret used for authentication
32+
JWTTokenSecretName *string `json:"jwtTokenSecretName,omitempty"`
33+
}
34+
35+
// MetricsSpec contains spec for arangodb exporter
36+
type MetricsSpec struct {
37+
Enabled *bool `json:"enabled,omitempty"`
38+
Image *string `json:"image,omitempty"`
39+
Authentication MetricsAuthenticationSpec `json:"authentication,omitempty"`
40+
}
41+
42+
// IsEnabled returns whether metrics are enabled or not
43+
func (s *MetricsSpec) IsEnabled() bool {
44+
return util.BoolOrDefault(s.Enabled, false)
45+
}
46+
47+
// HasImage returns whether a image was specified or not
48+
func (s *MetricsSpec) HasImage() bool {
49+
return s.Image != nil
50+
}
51+
52+
// GetImage returns the Image or empty string
53+
func (s *MetricsSpec) GetImage() string {
54+
return util.StringOrDefault(s.Image)
55+
}
56+
57+
// SetDefaults sets default values
58+
func (s *MetricsSpec) SetDefaults(defaultTokenName string, isAuthenticated bool) {
59+
if s.Enabled == nil {
60+
s.Enabled = util.NewBool(false)
61+
}
62+
if s.GetJWTTokenSecretName() == "" {
63+
s.Authentication.JWTTokenSecretName = util.NewString(defaultTokenName)
64+
}
65+
}
66+
67+
// GetJWTTokenSecretName returns the token secret name or empty string
68+
func (s *MetricsSpec) GetJWTTokenSecretName() string {
69+
return util.StringOrDefault(s.Authentication.JWTTokenSecretName)
70+
}
71+
72+
// HasJWTTokenSecretName returns true if a secret name was specified
73+
func (s *MetricsSpec) HasJWTTokenSecretName() bool {
74+
return s.Authentication.JWTTokenSecretName != nil
75+
}
76+
77+
// SetDefaultsFrom fills unspecified fields with a value from given source spec.
78+
func (s *MetricsSpec) SetDefaultsFrom(source MetricsSpec) {
79+
if s.Enabled == nil {
80+
s.Enabled = util.NewBoolOrNil(source.Enabled)
81+
}
82+
if s.Image == nil {
83+
s.Image = util.NewStringOrNil(source.Image)
84+
}
85+
if s.Authentication.JWTTokenSecretName == nil {
86+
s.Authentication.JWTTokenSecretName = util.NewStringOrNil(source.Authentication.JWTTokenSecretName)
87+
}
88+
}
89+
90+
// Validate the given spec
91+
func (s *MetricsSpec) Validate() error {
92+
93+
if s.HasJWTTokenSecretName() {
94+
if err := k8sutil.ValidateResourceName(s.GetJWTTokenSecretName()); err != nil {
95+
return err
96+
}
97+
}
98+
99+
return nil
100+
}
101+
102+
// ResetImmutableFields replaces all immutable fields in the given target with values from the source spec.
103+
func (s MetricsSpec) ResetImmutableFields(fieldPrefix string, target *MetricsSpec) []string {
104+
return nil
105+
}

pkg/apis/deployment/v1alpha/server_group.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,13 @@ func (g ServerGroup) IsArangosync() bool {
130130
return false
131131
}
132132
}
133+
134+
// IsExportMetrics return true when the group can be used with the arangodbexporter
135+
func (g ServerGroup) IsExportMetrics() bool {
136+
switch g {
137+
case ServerGroupCoordinators, ServerGroupDBServers, ServerGroupSingle:
138+
return true
139+
default:
140+
return false
141+
}
142+
}

pkg/apis/deployment/v1alpha/zz_generated.deepcopy.go

Lines changed: 49 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/deployment/images.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ func (ib *imagesBuilder) fetchArangoDBImageIDAndVersion(ctx context.Context, ima
198198
}
199199
}
200200
if err := k8sutil.CreateArangodPod(ib.KubeCli, true, ib.APIObject, role, id, podName, "", image, "", "", ib.Spec.GetImagePullPolicy(), "", false, terminationGracePeriod, args, env, nil, nil, nil,
201-
tolerations, serviceAccountName, "", "", "", nil, "", v1.ResourceRequirements{}); err != nil {
201+
tolerations, serviceAccountName, "", "", "", nil, "", v1.ResourceRequirements{}, nil); err != nil {
202202
log.Debug().Err(err).Msg("Failed to create image ID pod")
203203
return true, maskAny(err)
204204
}

pkg/deployment/reconcile/plan_builder.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,24 @@ func podNeedsRotation(log zerolog.Logger, p v1.Pod, apiObject metav1.Object, spe
343343
return false, "Server Image not found"
344344
}
345345

346+
if group.IsExportMetrics() {
347+
e, hasExporter := k8sutil.GetContainerByName(&p, k8sutil.ExporterContainerName)
348+
349+
if spec.Metrics.IsEnabled() {
350+
if !hasExporter {
351+
return true, "Exporter configuration changed"
352+
}
353+
354+
if spec.Metrics.HasImage() {
355+
if e.Image != spec.Metrics.GetImage() {
356+
return true, "Exporter image changed"
357+
}
358+
}
359+
} else if hasExporter {
360+
return true, "Exporter was disabled"
361+
}
362+
}
363+
346364
// Check arguments
347365
expectedArgs := strings.Join(context.GetExpectedPodArguments(apiObject, spec, group, status.Members.Agents, id, podImageInfo.ArangoDBVersion), " ")
348366
actualArgs := strings.Join(getContainerArgs(c), " ")

pkg/deployment/resources/pod_creator.go

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,30 @@ func createArangoSyncArgs(apiObject metav1.Object, spec api.DeploymentSpec, grou
329329
return args
330330
}
331331

332+
func createExporterArgs(isSecure bool) []string {
333+
tokenpath := filepath.Join(k8sutil.ExporterJWTVolumeMountDir, constants.SecretKeyToken)
334+
options := make([]optionPair, 0, 64)
335+
options = append(options,
336+
optionPair{"--arangodb.jwt-file", tokenpath},
337+
optionPair{"--arangodb.endpoint", "http://localhost:" + strconv.Itoa(k8sutil.ArangoPort)},
338+
)
339+
keyPath := filepath.Join(k8sutil.TLSKeyfileVolumeMountDir, constants.SecretTLSKeyfile)
340+
if isSecure {
341+
options = append(options,
342+
optionPair{"--ssl.keyfile", keyPath},
343+
)
344+
}
345+
args := make([]string, 0, 2+len(options))
346+
sort.Slice(options, func(i, j int) bool {
347+
return options[i].CompareTo(options[j]) < 0
348+
})
349+
for _, o := range options {
350+
args = append(args, o.Key+"="+o.Value)
351+
}
352+
353+
return args
354+
}
355+
332356
// createLivenessProbe creates configuration for a liveness probe of a server in the given group.
333357
func (r *Resources) createLivenessProbe(spec api.DeploymentSpec, group api.ServerGroup) (*k8sutil.HTTPProbeConfig, error) {
334358
groupspec := spec.GetServerGroupSpec(group)
@@ -497,6 +521,16 @@ func (r *Resources) createPodTolerations(group api.ServerGroup, groupSpec api.Se
497521
return tolerations
498522
}
499523

524+
func createExporterLivenessProbe(isSecure bool) *k8sutil.HTTPProbeConfig {
525+
probeCfg := &k8sutil.HTTPProbeConfig{
526+
LocalPath: "/",
527+
Port: k8sutil.ArangoExporterPort,
528+
Secure: isSecure,
529+
}
530+
531+
return probeCfg
532+
}
533+
500534
// createPodForMember creates all Pods listed in member status
501535
func (r *Resources) createPodForMember(spec api.DeploymentSpec, memberID string, imageNotFoundOnce *sync.Once) error {
502536
kubecli := r.context.GetKubeCli()
@@ -604,12 +638,29 @@ func (r *Resources) createPodForMember(spec api.DeploymentSpec, memberID string,
604638
}
605639
}
606640

641+
var exporter *k8sutil.ArangodbExporterContainerConf
642+
643+
if spec.Metrics.IsEnabled() {
644+
if group.IsExportMetrics() {
645+
image := spec.GetImage()
646+
if spec.Metrics.HasImage() {
647+
image = spec.Metrics.GetImage()
648+
}
649+
exporter = &k8sutil.ArangodbExporterContainerConf{
650+
Args: createExporterArgs(spec.IsSecure()),
651+
JWTTokenSecretName: spec.Metrics.GetJWTTokenSecretName(),
652+
LivenessProbe: createExporterLivenessProbe(spec.IsSecure()),
653+
Image: image,
654+
}
655+
}
656+
}
657+
607658
engine := spec.GetStorageEngine().AsArangoArgument()
608659
requireUUID := group == api.ServerGroupDBServers && m.IsInitialized
609660
finalizers := r.createPodFinalizers(group)
610661
if err := k8sutil.CreateArangodPod(kubecli, spec.IsDevelopment(), apiObject, role, m.ID, m.PodName, m.PersistentVolumeClaimName, imageInfo.ImageID, lifecycleImage, alpineImage, spec.GetImagePullPolicy(),
611662
engine, requireUUID, terminationGracePeriod, args, env, finalizers, livenessProbe, readinessProbe, tolerations, serviceAccountName, tlsKeyfileSecretName, rocksdbEncryptionSecretName,
612-
clusterJWTSecretName, groupSpec.GetNodeSelector(), groupSpec.PriorityClassName, groupSpec.Resources); err != nil {
663+
clusterJWTSecretName, groupSpec.GetNodeSelector(), groupSpec.PriorityClassName, groupSpec.Resources, exporter); err != nil {
613664
return maskAny(err)
614665
}
615666
log.Debug().Str("pod-name", m.PodName).Msg("Created pod")

pkg/deployment/resources/secrets.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ func (r *Resources) EnsureSecrets() error {
5555
if err := r.ensureTokenSecret(secrets, spec.Authentication.GetJWTSecretName()); err != nil {
5656
return maskAny(err)
5757
}
58+
59+
if spec.Metrics.IsEnabled() {
60+
if err := r.ensureExporterTokenSecret(secrets, spec.Metrics.GetJWTTokenSecretName(), spec.Authentication.GetJWTSecretName()); err != nil {
61+
return maskAny(err)
62+
}
63+
}
5864
}
5965
if spec.IsSecure() {
6066
counterMetric.Inc()
@@ -110,6 +116,32 @@ func (r *Resources) ensureTokenSecret(secrets k8sutil.SecretInterface, secretNam
110116
return nil
111117
}
112118

119+
// ensureExporterTokenSecret checks if a secret with given name exists in the namespace
120+
// of the deployment. If not, it will add such a secret with correct access.
121+
func (r *Resources) ensureExporterTokenSecret(secrets k8sutil.SecretInterface, tokenSecretName, secretSecretName string) error {
122+
if _, err := secrets.Get(tokenSecretName, metav1.GetOptions{}); k8sutil.IsNotFound(err) {
123+
// Secret not found, create it
124+
claims := map[string]interface{}{
125+
"iss": "arangodb",
126+
"server_id": "exporter",
127+
"allowed_paths": []string{"/_admin/statistics", "/_admin/statistics-description"},
128+
}
129+
// Create secret
130+
owner := r.context.GetAPIObject().AsOwner()
131+
if err := k8sutil.CreateJWTFromSecret(secrets, tokenSecretName, secretSecretName, claims, &owner); k8sutil.IsAlreadyExists(err) {
132+
// Secret added while we tried it also
133+
return nil
134+
} else if err != nil {
135+
// Failed to create secret
136+
return maskAny(err)
137+
}
138+
} else if err != nil {
139+
// Failed to get secret for other reasons
140+
return maskAny(err)
141+
}
142+
return nil
143+
}
144+
113145
// ensureTLSCACertificateSecret checks if a secret with given name exists in the namespace
114146
// of the deployment. If not, it will add such a secret with a generated CA certificate.
115147
func (r *Resources) ensureTLSCACertificateSecret(secrets k8sutil.SecretInterface, spec api.TLSSpec) error {

pkg/util/k8sutil/constants.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const (
2727
ArangoPort = 8529
2828
ArangoSyncMasterPort = 8629
2929
ArangoSyncWorkerPort = 8729
30+
ArangoExporterPort = 9101
3031

3132
// K8s constants
3233
ClusterIPNone = "None"

0 commit comments

Comments
 (0)