github.com/argoproj/argo-events@v1.9.1/controllers/eventbus/installer/jetstream.go (about) 1 package installer 2 3 import ( 4 "bytes" 5 "context" 6 "embed" 7 "fmt" 8 "strconv" 9 "strings" 10 "text/template" 11 "time" 12 13 "github.com/spf13/viper" 14 "go.uber.org/zap" 15 appv1 "k8s.io/api/apps/v1" 16 corev1 "k8s.io/api/core/v1" 17 apierrors "k8s.io/apimachinery/pkg/api/errors" 18 apiresource "k8s.io/apimachinery/pkg/api/resource" 19 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 20 "k8s.io/apimachinery/pkg/labels" 21 "k8s.io/apimachinery/pkg/runtime/schema" 22 "k8s.io/apimachinery/pkg/util/intstr" 23 "k8s.io/client-go/kubernetes" 24 "sigs.k8s.io/controller-runtime/pkg/client" 25 "sigs.k8s.io/yaml" 26 27 "github.com/argoproj/argo-events/common" 28 "github.com/argoproj/argo-events/common/tls" 29 "github.com/argoproj/argo-events/controllers" 30 "github.com/argoproj/argo-events/pkg/apis/eventbus/v1alpha1" 31 ) 32 33 const ( 34 jsClientPort = int32(4222) 35 jsClusterPort = int32(6222) 36 jsMonitorPort = int32(8222) 37 jsMetricsPort = int32(7777) 38 ) 39 40 var ( 41 //go:embed assets/jetstream/* 42 jetStremAssets embed.FS 43 ) 44 45 const ( 46 secretServerKeyPEMFile = "server-key.pem" 47 secretServerCertPEMFile = "server-cert.pem" 48 secretCACertPEMFile = "ca-cert.pem" 49 50 secretClusterKeyPEMFile = "cluster-server-key.pem" 51 secretClusterCertPEMFile = "cluster-server-cert.pem" 52 secretClusterCACertPEMFile = "cluster-ca-cert.pem" 53 54 certOrg = "io.argoproj" 55 ) 56 57 type jetStreamInstaller struct { 58 client client.Client 59 eventBus *v1alpha1.EventBus 60 kubeClient kubernetes.Interface 61 config *controllers.GlobalConfig 62 labels map[string]string 63 logger *zap.SugaredLogger 64 } 65 66 func NewJetStreamInstaller(client client.Client, eventBus *v1alpha1.EventBus, config *controllers.GlobalConfig, labels map[string]string, kubeClient kubernetes.Interface, logger *zap.SugaredLogger) Installer { 67 return &jetStreamInstaller{ 68 client: client, 69 kubeClient: kubeClient, 70 eventBus: eventBus, 71 config: config, 72 labels: labels, 73 logger: logger.With("eventbus", eventBus.Name), 74 } 75 } 76 77 func (r *jetStreamInstaller) Install(ctx context.Context) (*v1alpha1.BusConfig, error) { 78 if js := r.eventBus.Spec.JetStream; js == nil { 79 return nil, fmt.Errorf("invalid jetstream eventbus spec") 80 } 81 // merge 82 v := viper.New() 83 v.SetConfigType("yaml") 84 if err := v.ReadConfig(bytes.NewBufferString(r.config.EventBus.JetStream.StreamConfig)); err != nil { 85 return nil, fmt.Errorf("invalid jetstream config in global configuration, %w", err) 86 } 87 if x := r.eventBus.Spec.JetStream.StreamConfig; x != nil { 88 if err := v.MergeConfig(bytes.NewBufferString(*x)); err != nil { 89 return nil, fmt.Errorf("failed to merge customized stream config, %w", err) 90 } 91 } 92 b, err := yaml.Marshal(v.AllSettings()) 93 if err != nil { 94 return nil, fmt.Errorf("failed to marshal merged buffer config, %w", err) 95 } 96 97 if err := r.createSecrets(ctx); err != nil { 98 r.logger.Errorw("failed to create jetstream auth secrets", zap.Error(err)) 99 r.eventBus.Status.MarkDeployFailed("JetStreamAuthSecretsFailed", err.Error()) 100 return nil, err 101 } 102 if err := r.createConfigMap(ctx); err != nil { 103 r.logger.Errorw("failed to create jetstream ConfigMap", zap.Error(err)) 104 r.eventBus.Status.MarkDeployFailed("JetStreamConfigMapFailed", err.Error()) 105 return nil, err 106 } 107 if err := r.createService(ctx); err != nil { 108 r.logger.Errorw("failed to create jetstream Service", zap.Error(err)) 109 r.eventBus.Status.MarkDeployFailed("JetStreamServiceFailed", err.Error()) 110 return nil, err 111 } 112 if err := r.createStatefulSet(ctx); err != nil { 113 r.logger.Errorw("failed to create jetstream StatefulSet", zap.Error(err)) 114 r.eventBus.Status.MarkDeployFailed("JetStreamStatefulSetFailed", err.Error()) 115 return nil, err 116 } 117 r.eventBus.Status.MarkDeployed("Succeeded", "JetStream is deployed") 118 return &v1alpha1.BusConfig{ 119 JetStream: &v1alpha1.JetStreamConfig{ 120 URL: fmt.Sprintf("nats://%s.%s.svc:%s", generateJetStreamServiceName(r.eventBus), r.eventBus.Namespace, strconv.Itoa(int(jsClientPort))), 121 AccessSecret: &corev1.SecretKeySelector{ 122 LocalObjectReference: corev1.LocalObjectReference{ 123 Name: generateJetStreamClientAuthSecretName(r.eventBus), 124 }, 125 Key: common.JetStreamClientAuthSecretKey, 126 }, 127 StreamConfig: string(b), 128 }, 129 }, nil 130 } 131 132 // buildJetStreamService builds a Service for Jet Stream 133 func (r *jetStreamInstaller) buildJetStreamServiceSpec() corev1.ServiceSpec { 134 return corev1.ServiceSpec{ 135 Ports: []corev1.ServicePort{ 136 {Name: "tcp-client", Port: jsClientPort}, 137 {Name: "cluster", Port: jsClusterPort}, 138 {Name: "metrics", Port: jsMetricsPort}, 139 {Name: "monitor", Port: jsMonitorPort}, 140 }, 141 Type: corev1.ServiceTypeClusterIP, 142 ClusterIP: corev1.ClusterIPNone, 143 PublishNotReadyAddresses: true, 144 Selector: r.labels, 145 } 146 } 147 148 func (r *jetStreamInstaller) createService(ctx context.Context) error { 149 spec := r.buildJetStreamServiceSpec() 150 hash := common.MustHash(spec) 151 obj := &corev1.Service{ 152 ObjectMeta: metav1.ObjectMeta{ 153 Namespace: r.eventBus.Namespace, 154 Name: generateJetStreamServiceName(r.eventBus), 155 Labels: r.labels, 156 Annotations: map[string]string{ 157 common.AnnotationResourceSpecHash: hash, 158 }, 159 OwnerReferences: []metav1.OwnerReference{ 160 *metav1.NewControllerRef(r.eventBus.GetObjectMeta(), v1alpha1.SchemaGroupVersionKind), 161 }, 162 }, 163 Spec: spec, 164 } 165 old := &corev1.Service{} 166 if err := r.client.Get(ctx, client.ObjectKeyFromObject(obj), old); err != nil { 167 if apierrors.IsNotFound(err) { 168 if err := r.client.Create(ctx, obj); err != nil { 169 return fmt.Errorf("failed to create jetstream service, err: %w", err) 170 } 171 r.logger.Info("created jetstream service successfully") 172 return nil 173 } else { 174 return fmt.Errorf("failed to check if jetstream service is existing, err: %w", err) 175 } 176 } 177 if old.GetAnnotations()[common.AnnotationResourceSpecHash] != hash { 178 old.Annotations[common.AnnotationResourceSpecHash] = hash 179 old.Spec = spec 180 if err := r.client.Update(ctx, old); err != nil { 181 return fmt.Errorf("failed to update jetstream service, err: %w", err) 182 } 183 r.logger.Info("updated jetstream service successfully") 184 } 185 return nil 186 } 187 188 func (r *jetStreamInstaller) createStatefulSet(ctx context.Context) error { 189 jsVersion, err := r.config.GetJetStreamVersion(r.eventBus.Spec.JetStream.Version) 190 if err != nil { 191 return fmt.Errorf("failed to get jetstream version, err: %w", err) 192 } 193 spec := r.buildStatefulSetSpec(jsVersion) 194 hash := common.MustHash(spec) 195 obj := &appv1.StatefulSet{ 196 ObjectMeta: metav1.ObjectMeta{ 197 Namespace: r.eventBus.Namespace, 198 Name: generateJetStreamStatefulSetName(r.eventBus), 199 Labels: r.mergeEventBusLabels(r.labels), 200 Annotations: map[string]string{ 201 common.AnnotationResourceSpecHash: hash, 202 }, 203 OwnerReferences: []metav1.OwnerReference{ 204 *metav1.NewControllerRef(r.eventBus.GetObjectMeta(), v1alpha1.SchemaGroupVersionKind), 205 }, 206 }, 207 Spec: spec, 208 } 209 old := &appv1.StatefulSet{} 210 if err := r.client.Get(ctx, client.ObjectKeyFromObject(obj), old); err != nil { 211 if apierrors.IsNotFound(err) { 212 if err := r.client.Create(ctx, obj); err != nil { 213 return fmt.Errorf("failed to create jetstream statefulset, err: %w", err) 214 } 215 r.logger.Info("created jetstream statefulset successfully") 216 return nil 217 } else { 218 return fmt.Errorf("failed to check if jetstream statefulset is existing, err: %w", err) 219 } 220 } 221 if old.GetAnnotations()[common.AnnotationResourceSpecHash] != hash { 222 old.Annotations[common.AnnotationResourceSpecHash] = hash 223 old.Spec = spec 224 if err := r.client.Update(ctx, old); err != nil { 225 return fmt.Errorf("failed to update jetstream statefulset, err: %w", err) 226 } 227 r.logger.Info("updated jetstream statefulset successfully") 228 } 229 return nil 230 } 231 232 func (r *jetStreamInstaller) buildStatefulSetSpec(jsVersion *controllers.JetStreamVersion) appv1.StatefulSetSpec { 233 js := r.eventBus.Spec.JetStream 234 replicas := int32(js.GetReplicas()) 235 podTemplateLabels := make(map[string]string) 236 if js.Metadata != nil && 237 len(js.Metadata.Labels) > 0 { 238 for k, v := range js.Metadata.Labels { 239 podTemplateLabels[k] = v 240 } 241 } 242 for k, v := range r.labels { 243 podTemplateLabels[k] = v 244 } 245 var jsContainerPullPolicy, reloaderContainerPullPolicy, metricsContainerPullPolicy corev1.PullPolicy 246 var jsContainerSecurityContext, reloaderContainerSecurityContext, metricsContainerSecurityContext *corev1.SecurityContext 247 if js.ContainerTemplate != nil { 248 jsContainerPullPolicy = js.ContainerTemplate.ImagePullPolicy 249 jsContainerSecurityContext = js.ContainerTemplate.SecurityContext 250 } 251 if js.ReloaderContainerTemplate != nil { 252 reloaderContainerPullPolicy = js.ReloaderContainerTemplate.ImagePullPolicy 253 reloaderContainerSecurityContext = js.ReloaderContainerTemplate.SecurityContext 254 } 255 if js.MetricsContainerTemplate != nil { 256 metricsContainerPullPolicy = js.MetricsContainerTemplate.ImagePullPolicy 257 metricsContainerSecurityContext = js.MetricsContainerTemplate.SecurityContext 258 } 259 shareProcessNamespace := true 260 terminationGracePeriodSeconds := int64(60) 261 spec := appv1.StatefulSetSpec{ 262 PodManagementPolicy: appv1.ParallelPodManagement, 263 Replicas: &replicas, 264 ServiceName: generateJetStreamServiceName(r.eventBus), 265 Selector: &metav1.LabelSelector{ 266 MatchLabels: r.labels, 267 }, 268 Template: corev1.PodTemplateSpec{ 269 ObjectMeta: metav1.ObjectMeta{ 270 Labels: podTemplateLabels, 271 }, 272 Spec: corev1.PodSpec{ 273 NodeSelector: js.NodeSelector, 274 Tolerations: js.Tolerations, 275 SecurityContext: js.SecurityContext, 276 ImagePullSecrets: js.ImagePullSecrets, 277 PriorityClassName: js.PriorityClassName, 278 Priority: js.Priority, 279 ServiceAccountName: js.ServiceAccountName, 280 Affinity: js.Affinity, 281 ShareProcessNamespace: &shareProcessNamespace, 282 TerminationGracePeriodSeconds: &terminationGracePeriodSeconds, 283 Volumes: []corev1.Volume{ 284 {Name: "pid", VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}}, 285 { 286 Name: "config-volume", 287 VolumeSource: corev1.VolumeSource{ 288 Projected: &corev1.ProjectedVolumeSource{ 289 Sources: []corev1.VolumeProjection{ 290 { 291 ConfigMap: &corev1.ConfigMapProjection{ 292 LocalObjectReference: corev1.LocalObjectReference{ 293 Name: generateJetStreamConfigMapName(r.eventBus), 294 }, 295 Items: []corev1.KeyToPath{ 296 { 297 Key: common.JetStreamConfigMapKey, 298 Path: "nats-js.conf", 299 }, 300 }, 301 }, 302 }, 303 { 304 Secret: &corev1.SecretProjection{ 305 LocalObjectReference: corev1.LocalObjectReference{ 306 Name: generateJetStreamServerSecretName(r.eventBus), 307 }, 308 Items: []corev1.KeyToPath{ 309 { 310 Key: common.JetStreamServerSecretAuthKey, 311 Path: "auth.conf", 312 }, 313 { 314 Key: common.JetStreamServerPrivateKeyKey, 315 Path: secretServerKeyPEMFile, 316 }, 317 { 318 Key: common.JetStreamServerCertKey, 319 Path: secretServerCertPEMFile, 320 }, 321 { 322 Key: common.JetStreamServerCACertKey, 323 Path: secretCACertPEMFile, 324 }, 325 { 326 Key: common.JetStreamClusterPrivateKeyKey, 327 Path: secretClusterKeyPEMFile, 328 }, 329 { 330 Key: common.JetStreamClusterCertKey, 331 Path: secretClusterCertPEMFile, 332 }, 333 { 334 Key: common.JetStreamClusterCACertKey, 335 Path: secretClusterCACertPEMFile, 336 }, 337 }, 338 }, 339 }, 340 }, 341 }, 342 }, 343 }, 344 }, 345 Containers: []corev1.Container{ 346 { 347 Name: "main", 348 Image: jsVersion.NatsImage, 349 ImagePullPolicy: jsContainerPullPolicy, 350 Ports: []corev1.ContainerPort{ 351 {Name: "client", ContainerPort: jsClientPort}, 352 {Name: "cluster", ContainerPort: jsClusterPort}, 353 {Name: "monitor", ContainerPort: jsMonitorPort}, 354 }, 355 Command: []string{jsVersion.StartCommand, "--config", "/etc/nats-config/nats-js.conf"}, 356 Args: js.StartArgs, 357 Env: []corev1.EnvVar{ 358 {Name: "POD_NAME", ValueFrom: &corev1.EnvVarSource{FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.name"}}}, 359 {Name: "SERVER_NAME", Value: "$(POD_NAME)"}, 360 {Name: "POD_NAMESPACE", ValueFrom: &corev1.EnvVarSource{FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.namespace"}}}, 361 {Name: "CLUSTER_ADVERTISE", Value: "$(POD_NAME)." + generateJetStreamServiceName(r.eventBus) + ".$(POD_NAMESPACE).svc"}, 362 {Name: "JS_KEY", ValueFrom: &corev1.EnvVarSource{SecretKeyRef: &corev1.SecretKeySelector{LocalObjectReference: corev1.LocalObjectReference{Name: generateJetStreamServerSecretName(r.eventBus)}, Key: common.JetStreamServerSecretEncryptionKey}}}, 363 }, 364 VolumeMounts: []corev1.VolumeMount{ 365 {Name: "config-volume", MountPath: "/etc/nats-config"}, 366 {Name: "pid", MountPath: "/var/run/nats"}, 367 }, 368 SecurityContext: jsContainerSecurityContext, 369 StartupProbe: &corev1.Probe{ 370 ProbeHandler: corev1.ProbeHandler{ 371 HTTPGet: &corev1.HTTPGetAction{ 372 Path: "/healthz", 373 Port: intstr.FromInt(int(jsMonitorPort)), 374 }, 375 }, 376 FailureThreshold: 30, 377 InitialDelaySeconds: 10, 378 TimeoutSeconds: 5, 379 }, 380 LivenessProbe: &corev1.Probe{ 381 ProbeHandler: corev1.ProbeHandler{ 382 HTTPGet: &corev1.HTTPGetAction{ 383 Path: "/", 384 Port: intstr.FromInt(int(jsMonitorPort)), 385 }, 386 }, 387 InitialDelaySeconds: 10, 388 PeriodSeconds: 30, 389 TimeoutSeconds: 5, 390 }, 391 Lifecycle: &corev1.Lifecycle{ 392 PreStop: &corev1.LifecycleHandler{ 393 Exec: &corev1.ExecAction{ 394 Command: []string{jsVersion.StartCommand, "-sl=ldm=/var/run/nats/nats.pid"}, 395 }, 396 }, 397 }, 398 }, 399 { 400 Name: "reloader", 401 Image: jsVersion.ConfigReloaderImage, 402 ImagePullPolicy: reloaderContainerPullPolicy, 403 SecurityContext: reloaderContainerSecurityContext, 404 Command: []string{"nats-server-config-reloader", "-pid", "/var/run/nats/nats.pid", "-config", "/etc/nats-config/nats-js.conf"}, 405 VolumeMounts: []corev1.VolumeMount{ 406 {Name: "config-volume", MountPath: "/etc/nats-config"}, 407 {Name: "pid", MountPath: "/var/run/nats"}, 408 }, 409 }, 410 { 411 Name: "metrics", 412 Image: jsVersion.MetricsExporterImage, 413 ImagePullPolicy: metricsContainerPullPolicy, 414 Ports: []corev1.ContainerPort{ 415 {Name: "metrics", ContainerPort: jsMetricsPort}, 416 }, 417 Args: []string{"-connz", "-routez", "-subz", "-varz", "-prefix=nats", "-use_internal_server_id", "-jsz=all", fmt.Sprintf("http://localhost:%s", strconv.Itoa(int(jsMonitorPort)))}, 418 SecurityContext: metricsContainerSecurityContext, 419 }, 420 }, 421 }, 422 }, 423 } 424 if js.Metadata != nil { 425 spec.Template.SetAnnotations(js.Metadata.Annotations) 426 } 427 428 podContainers := spec.Template.Spec.Containers 429 containers := map[string]*corev1.Container{} 430 for idx := range podContainers { 431 containers[podContainers[idx].Name] = &podContainers[idx] 432 } 433 434 if js.ContainerTemplate != nil { 435 containers["main"].Resources = js.ContainerTemplate.Resources 436 } 437 438 if js.MetricsContainerTemplate != nil { 439 containers["metrics"].Resources = js.MetricsContainerTemplate.Resources 440 } 441 442 if js.ReloaderContainerTemplate != nil { 443 containers["reloader"].Resources = js.ReloaderContainerTemplate.Resources 444 } 445 446 if js.Persistence != nil { 447 volMode := corev1.PersistentVolumeFilesystem 448 // Default volume size 449 volSize := apiresource.MustParse("20Gi") 450 if js.Persistence.VolumeSize != nil { 451 volSize = *js.Persistence.VolumeSize 452 } 453 // Default to ReadWriteOnce 454 accessMode := corev1.ReadWriteOnce 455 if js.Persistence.AccessMode != nil { 456 accessMode = *js.Persistence.AccessMode 457 } 458 spec.VolumeClaimTemplates = []corev1.PersistentVolumeClaim{ 459 { 460 ObjectMeta: metav1.ObjectMeta{ 461 Name: generateJetStreamPVCName(r.eventBus), 462 }, 463 Spec: corev1.PersistentVolumeClaimSpec{ 464 AccessModes: []corev1.PersistentVolumeAccessMode{ 465 accessMode, 466 }, 467 VolumeMode: &volMode, 468 StorageClassName: js.Persistence.StorageClassName, 469 Resources: corev1.ResourceRequirements{ 470 Requests: corev1.ResourceList{ 471 corev1.ResourceStorage: volSize, 472 }, 473 }, 474 }, 475 }, 476 } 477 volumeMounts := spec.Template.Spec.Containers[0].VolumeMounts 478 volumeMounts = append(volumeMounts, corev1.VolumeMount{Name: generateJetStreamPVCName(r.eventBus), MountPath: "/data/jetstream"}) 479 spec.Template.Spec.Containers[0].VolumeMounts = volumeMounts 480 } else { 481 // When the POD is runasnonroot, it can not create the dir /data/jetstream 482 // Use an emptyDirVolume 483 emptyDirVolName := "js-data" 484 volumes := spec.Template.Spec.Volumes 485 volumes = append(volumes, corev1.Volume{Name: emptyDirVolName, VolumeSource: corev1.VolumeSource{EmptyDir: &corev1.EmptyDirVolumeSource{}}}) 486 spec.Template.Spec.Volumes = volumes 487 volumeMounts := spec.Template.Spec.Containers[0].VolumeMounts 488 volumeMounts = append(volumeMounts, corev1.VolumeMount{Name: emptyDirVolName, MountPath: "/data/jetstream"}) 489 spec.Template.Spec.Containers[0].VolumeMounts = volumeMounts 490 } 491 return spec 492 } 493 494 func (r *jetStreamInstaller) getSecret(ctx context.Context, name string) (*corev1.Secret, error) { 495 sl, err := r.kubeClient.CoreV1().Secrets(r.eventBus.Namespace).List(ctx, metav1.ListOptions{}) 496 if err != nil { 497 return nil, err 498 } 499 for _, s := range sl.Items { 500 if s.Name == name && metav1.IsControlledBy(&s, r.eventBus) { 501 return &s, nil 502 } 503 } 504 return nil, apierrors.NewNotFound(schema.GroupResource{}, "") 505 } 506 507 func (r *jetStreamInstaller) createSecrets(ctx context.Context) error { 508 // first check to see if the secrets already exist 509 oldServerObjExisting, oldClientObjExisting := true, true 510 511 oldSObj, err := r.getSecret(ctx, generateJetStreamServerSecretName(r.eventBus)) 512 if err != nil { 513 if apierrors.IsNotFound(err) { 514 oldServerObjExisting = false 515 } else { 516 return fmt.Errorf("failed to check if nats server auth secret is existing, err: %w", err) 517 } 518 } 519 520 oldCObj, err := r.getSecret(ctx, generateJetStreamClientAuthSecretName(r.eventBus)) 521 if err != nil { 522 if apierrors.IsNotFound(err) { 523 oldClientObjExisting = false 524 } else { 525 return fmt.Errorf("failed to check if nats client auth secret is existing, err: %w", err) 526 } 527 } 528 529 if !oldClientObjExisting || !oldServerObjExisting { 530 // Generate server-auth.conf file 531 encryptionKey := common.RandomString(12) 532 jsUser := common.RandomString(8) 533 jsPass := common.RandomString(16) 534 sysPassword := common.RandomString(24) 535 authTpl := template.Must(template.ParseFS(jetStremAssets, "assets/jetstream/server-auth.conf")) 536 var authTplOutput bytes.Buffer 537 if err := authTpl.Execute(&authTplOutput, struct { 538 JetStreamUser string 539 JetStreamPassword string 540 SysPassword string 541 }{ 542 JetStreamUser: jsUser, 543 JetStreamPassword: jsPass, 544 SysPassword: sysPassword, 545 }); err != nil { 546 return fmt.Errorf("failed to parse nats auth template, error: %w", err) 547 } 548 549 // Generate TLS self signed certificate for Jetstream bus: includes TLS private key, certificate, and CA certificate 550 hosts := []string{} 551 hosts = append(hosts, fmt.Sprintf("%s.%s.svc.cluster.local", generateJetStreamServiceName(r.eventBus), r.eventBus.Namespace)) // todo: get an error in the log file related to this: do we need it? 552 hosts = append(hosts, fmt.Sprintf("%s.%s.svc", generateJetStreamServiceName(r.eventBus), r.eventBus.Namespace)) 553 554 serverKeyPEM, serverCertPEM, caCertPEM, err := tls.CreateCerts(certOrg, hosts, time.Now().Add(10*365*24*time.Hour), true, false) // expires in 10 years 555 if err != nil { 556 return err 557 } 558 559 // Generate TLS self signed certificate for Jetstream cluster nodes: includes TLS private key, certificate, and CA certificate 560 clusterNodeHosts := []string{ 561 fmt.Sprintf("*.%s.%s.svc.cluster.local", generateJetStreamServiceName(r.eventBus), r.eventBus.Namespace), 562 fmt.Sprintf("*.%s.%s.svc", generateJetStreamServiceName(r.eventBus), r.eventBus.Namespace), 563 } 564 r.logger.Infof("cluster node hosts: %+v", clusterNodeHosts) 565 clusterKeyPEM, clusterCertPEM, clusterCACertPEM, err := tls.CreateCerts(certOrg, clusterNodeHosts, time.Now().Add(10*365*24*time.Hour), true, true) // expires in 10 years 566 if err != nil { 567 return err 568 } 569 570 serverObj := &corev1.Secret{ 571 ObjectMeta: metav1.ObjectMeta{ 572 Namespace: r.eventBus.Namespace, 573 Name: generateJetStreamServerSecretName(r.eventBus), 574 Labels: r.labels, 575 OwnerReferences: []metav1.OwnerReference{ 576 *metav1.NewControllerRef(r.eventBus.GetObjectMeta(), v1alpha1.SchemaGroupVersionKind), 577 }, 578 }, 579 Type: corev1.SecretTypeOpaque, 580 Data: map[string][]byte{ 581 common.JetStreamServerSecretAuthKey: authTplOutput.Bytes(), 582 common.JetStreamServerSecretEncryptionKey: []byte(encryptionKey), 583 common.JetStreamServerPrivateKeyKey: serverKeyPEM, 584 common.JetStreamServerCertKey: serverCertPEM, 585 common.JetStreamServerCACertKey: caCertPEM, 586 common.JetStreamClusterPrivateKeyKey: clusterKeyPEM, 587 common.JetStreamClusterCertKey: clusterCertPEM, 588 common.JetStreamClusterCACertKey: clusterCACertPEM, 589 }, 590 } 591 592 clientAuthObj := &corev1.Secret{ 593 ObjectMeta: metav1.ObjectMeta{ 594 Namespace: r.eventBus.Namespace, 595 Name: generateJetStreamClientAuthSecretName(r.eventBus), 596 Labels: r.labels, 597 OwnerReferences: []metav1.OwnerReference{ 598 *metav1.NewControllerRef(r.eventBus.GetObjectMeta(), v1alpha1.SchemaGroupVersionKind), 599 }, 600 }, 601 Type: corev1.SecretTypeOpaque, 602 Data: map[string][]byte{ 603 common.JetStreamClientAuthSecretKey: []byte(fmt.Sprintf("username: %s\npassword: %s", jsUser, jsPass)), 604 }, 605 } 606 607 if oldServerObjExisting { 608 if err := r.client.Delete(ctx, oldSObj); err != nil { 609 return fmt.Errorf("failed to delete malformed nats server auth secret, err: %w", err) 610 } 611 r.logger.Infow("deleted malformed nats server auth secret successfully") 612 } 613 614 if oldClientObjExisting { 615 if err := r.client.Delete(ctx, oldCObj); err != nil { 616 return fmt.Errorf("failed to delete malformed nats client auth secret, err: %w", err) 617 } 618 r.logger.Infow("deleted malformed nats client auth secret successfully") 619 } 620 621 if err := r.client.Create(ctx, serverObj); err != nil { 622 return fmt.Errorf("failed to create nats server auth secret, err: %w", err) 623 } 624 r.logger.Infow("created nats server auth secret successfully") 625 626 if err := r.client.Create(ctx, clientAuthObj); err != nil { 627 return fmt.Errorf("failed to create nats client auth secret, err: %w", err) 628 } 629 r.logger.Infow("created nats client auth secret successfully") 630 } 631 632 return nil 633 } 634 635 func (r *jetStreamInstaller) createConfigMap(ctx context.Context) error { 636 data := make(map[string]string) 637 svcName := generateJetStreamServiceName(r.eventBus) 638 ssName := generateJetStreamStatefulSetName(r.eventBus) 639 replicas := r.eventBus.Spec.JetStream.GetReplicas() 640 routes := []string{} 641 for j := 0; j < replicas; j++ { 642 routes = append(routes, fmt.Sprintf("nats://%s-%s.%s.%s.svc:%s", ssName, strconv.Itoa(j), svcName, r.eventBus.Namespace, strconv.Itoa(int(jsClusterPort)))) 643 } 644 settings := r.config.EventBus.JetStream.Settings 645 if x := r.eventBus.Spec.JetStream.Settings; x != nil { 646 settings = *x 647 } 648 maxPayload := common.JetStreamMaxPayload 649 if r.eventBus.Spec.JetStream.MaxPayload != nil { 650 maxPayload = *r.eventBus.Spec.JetStream.MaxPayload 651 } 652 var confTpl *template.Template 653 if replicas > 2 { 654 confTpl = template.Must(template.ParseFS(jetStremAssets, "assets/jetstream/nats-cluster.conf")) 655 } else { 656 confTpl = template.Must(template.ParseFS(jetStremAssets, "assets/jetstream/nats.conf")) 657 } 658 var confTplOutput bytes.Buffer 659 if err := confTpl.Execute(&confTplOutput, struct { 660 MaxPayloadSize string 661 ClusterName string 662 MonitorPort string 663 ClusterPort string 664 ClientPort string 665 Routes string 666 Settings string 667 }{ 668 MaxPayloadSize: maxPayload, 669 ClusterName: r.eventBus.Name, 670 MonitorPort: strconv.Itoa(int(jsMonitorPort)), 671 ClusterPort: strconv.Itoa(int(jsClusterPort)), 672 ClientPort: strconv.Itoa(int(jsClientPort)), 673 Routes: strings.Join(routes, ","), 674 Settings: settings, 675 }); err != nil { 676 return fmt.Errorf("failed to parse nats config template, error: %w", err) 677 } 678 data[common.JetStreamConfigMapKey] = confTplOutput.String() 679 680 hash := common.MustHash(data) 681 obj := &corev1.ConfigMap{ 682 ObjectMeta: metav1.ObjectMeta{ 683 Namespace: r.eventBus.Namespace, 684 Name: generateJetStreamConfigMapName(r.eventBus), 685 Labels: r.labels, 686 Annotations: map[string]string{ 687 common.AnnotationResourceSpecHash: hash, 688 }, 689 OwnerReferences: []metav1.OwnerReference{ 690 *metav1.NewControllerRef(r.eventBus.GetObjectMeta(), v1alpha1.SchemaGroupVersionKind), 691 }, 692 }, 693 Data: data, 694 } 695 old := &corev1.ConfigMap{} 696 if err := r.client.Get(ctx, client.ObjectKeyFromObject(obj), old); err != nil { 697 if apierrors.IsNotFound(err) { 698 if err := r.client.Create(ctx, obj); err != nil { 699 return fmt.Errorf("failed to create jetstream configmap, err: %w", err) 700 } 701 r.logger.Info("created jetstream configmap successfully") 702 return nil 703 } else { 704 return fmt.Errorf("failed to check if jetstream configmap is existing, err: %w", err) 705 } 706 } 707 if old.GetAnnotations()[common.AnnotationResourceSpecHash] != hash { 708 old.Annotations[common.AnnotationResourceSpecHash] = hash 709 old.Data = data 710 if err := r.client.Update(ctx, old); err != nil { 711 return fmt.Errorf("failed to update jetstream configmap, err: %w", err) 712 } 713 r.logger.Info("updated jetstream configmap successfully") 714 } 715 return nil 716 } 717 718 func (r *jetStreamInstaller) Uninstall(ctx context.Context) error { 719 return r.uninstallPVCs(ctx) 720 } 721 722 func (r *jetStreamInstaller) uninstallPVCs(ctx context.Context) error { 723 // StatefulSet doesn't clean up PVC, needs to do it separately 724 // https://github.com/kubernetes/kubernetes/issues/55045 725 pvcs, err := r.getPVCs(ctx) 726 if err != nil { 727 r.logger.Errorw("failed to get PVCs created by Nats statefulset when uninstalling", zap.Error(err)) 728 return err 729 } 730 for _, pvc := range pvcs { 731 err = r.client.Delete(ctx, &pvc) 732 if err != nil { 733 r.logger.Errorw("failed to delete pvc when uninstalling", zap.Any("pvcName", pvc.Name), zap.Error(err)) 734 return err 735 } 736 r.logger.Infow("pvc deleted", "pvcName", pvc.Name) 737 } 738 return nil 739 } 740 741 // get PVCs created by streaming statefulset 742 // they have same labels as the statefulset 743 func (r *jetStreamInstaller) getPVCs(ctx context.Context) ([]corev1.PersistentVolumeClaim, error) { 744 pvcl := &corev1.PersistentVolumeClaimList{} 745 err := r.client.List(ctx, pvcl, &client.ListOptions{ 746 Namespace: r.eventBus.Namespace, 747 LabelSelector: labels.SelectorFromSet(r.labels), 748 }) 749 if err != nil { 750 return nil, err 751 } 752 return pvcl.Items, nil 753 } 754 755 func generateJetStreamServerSecretName(eventBus *v1alpha1.EventBus) string { 756 return fmt.Sprintf("eventbus-%s-js-server", eventBus.Name) 757 } 758 759 func (r *jetStreamInstaller) mergeEventBusLabels(given map[string]string) map[string]string { 760 result := map[string]string{} 761 if r.eventBus.Labels != nil { 762 for k, v := range r.eventBus.Labels { 763 result[k] = v 764 } 765 } 766 for k, v := range given { 767 result[k] = v 768 } 769 return result 770 } 771 772 func generateJetStreamClientAuthSecretName(eventBus *v1alpha1.EventBus) string { 773 return fmt.Sprintf("eventbus-%s-js-client-auth", eventBus.Name) 774 } 775 776 func generateJetStreamServiceName(eventBus *v1alpha1.EventBus) string { 777 return fmt.Sprintf("eventbus-%s-js-svc", eventBus.Name) 778 } 779 780 func generateJetStreamStatefulSetName(eventBus *v1alpha1.EventBus) string { 781 return fmt.Sprintf("eventbus-%s-js", eventBus.Name) 782 } 783 784 func generateJetStreamConfigMapName(eventBus *v1alpha1.EventBus) string { 785 return fmt.Sprintf("eventbus-%s-js-config", eventBus.Name) 786 } 787 788 func generateJetStreamPVCName(eventBus *v1alpha1.EventBus) string { 789 return fmt.Sprintf("eventbus-%s-js-vol", eventBus.Name) 790 }