/// <summary> /// Build a <see cref="PrometheusServiceMonitorSpecV1"/> for the specified server. /// </summary> /// <param name="server"> /// A <see cref="DatabaseServer"/> representing the target server. /// </param> /// <returns> /// The configured <see cref="PrometheusServiceMonitorSpecV1"/>. /// </returns> public PrometheusServiceMonitorSpecV1 ServiceMonitor(DatabaseServer server) { if (server == null) { throw new ArgumentNullException(nameof(server)); } string baseName = Names.BaseName(server); return(new PrometheusServiceMonitorSpecV1 { JobLabel = baseName, Selector = new LabelSelectorV1 { MatchLabels = new Dictionary <string, string> { ["cloud.dimensiondata.daas.server-id"] = server.Id, ["cloud.dimensiondata.daas.service-type"] = "internal" } }, EndPoints = new List <PrometheusServiceMonitorEndPointV1> { new PrometheusServiceMonitorEndPointV1 { Port = "prometheus-exporter" } } }); }
/// <summary> /// Build an externally-facing <see cref="ServiceSpecV1"/> for the specified server. /// </summary> /// <param name="server"> /// A <see cref="DatabaseServer"/> representing the target server. /// </param> /// <returns> /// The configured <see cref="ServiceSpecV1"/>. /// </returns> public ServiceSpecV1 ExternalService(DatabaseServer server) { if (server == null) { throw new ArgumentNullException(nameof(server)); } string baseName = Names.BaseName(server); var spec = new ServiceSpecV1 { Type = "NodePort", Ports = new List <ServicePortV1>(), Selector = new Dictionary <string, string> { ["k8s-app"] = baseName } }; switch (server.Kind) { case DatabaseServerKind.SqlServer: { spec.Ports.Add(new ServicePortV1 { Name = "sql-server", Port = 1433, Protocol = "TCP" }); break; } case DatabaseServerKind.RavenDB: { spec.Ports.Add(new ServicePortV1 { Name = "http", Port = 8080, Protocol = "TCP" }); spec.Ports.Add(new ServicePortV1 { Name = "tcp", Port = 38888, Protocol = "TCP" }); break; } default: { throw new NotSupportedException($"Unsupported server type ({server.Kind})."); } } return(spec); }
/// <summary> /// Create a new internally-facing <see cref="ServiceV1"/> for the specified database server. /// </summary> /// <param name="server"> /// A <see cref="DatabaseServer"/> representing the target server. /// </param> /// <param name="kubeNamespace"> /// An optional target Kubernetes namespace. /// </param> /// <returns> /// The configured <see cref="ServiceV1"/>. /// </returns> public ServiceV1 InternalService(DatabaseServer server, string kubeNamespace = null) { if (server == null) { throw new ArgumentNullException(nameof(server)); } return(Service( name: Names.BaseName(server), kubeNamespace: kubeNamespace, spec: Specs.InternalService(server), labels: new Dictionary <string, string> { ["k8s-app"] = Names.BaseName(server), ["cloud.dimensiondata.daas.server-id"] = server.Id, ["cloud.dimensiondata.daas.service-type"] = "internal" } )); }
/// <summary> /// Create a new <see cref="PersistentVolumeClaimV1"/> for the specified database server. /// </summary> /// <param name="server"> /// A <see cref="DatabaseServer"/> representing the target server. /// </param> /// <param name="kubeNamespace"> /// An optional target Kubernetes namespace. /// </param> /// <returns> /// The configured <see cref="PersistentVolumeClaimV1"/>. /// </returns> public PersistentVolumeClaimV1 DataVolumeClaim(DatabaseServer server, string kubeNamespace = null) { if (server == null) { throw new ArgumentNullException(nameof(server)); } return(DataVolumeClaim( name: Names.DataVolumeClaim(server), kubeNamespace: kubeNamespace, spec: Specs.DataVolumeClaim(server), labels: new Dictionary <string, string> { ["k8s-app"] = Names.BaseName(server), ["cloud.dimensiondata.daas.server-id"] = server.Id, ["cloud.dimensiondata.daas.volume-type"] = "data" } )); }
/// <summary> /// Create a new <see cref="DeploymentV1Beta1"/> for the specified database server. /// </summary> /// <param name="server"> /// A <see cref="DatabaseServer"/> representing the target server. /// </param> /// <param name="kubeNamespace"> /// An optional target Kubernetes namespace. /// </param> /// <returns> /// The configured <see cref="DeploymentV1Beta1"/>. /// </returns> public DeploymentV1Beta1 Deployment(DatabaseServer server, string kubeNamespace = null) { if (server == null) { throw new ArgumentNullException(nameof(server)); } string baseName = Names.BaseName(server); return(Deployment( name: baseName, kubeNamespace: kubeNamespace, spec: Specs.Deployment(server), labels: new Dictionary <string, string> { ["k8s-app"] = baseName, ["cloud.dimensiondata.daas.server-id"] = server.Id } )); }
/// <summary> /// Create a new <see cref="PrometheusServiceMonitorV1"/> for the specified database server. /// </summary> /// <param name="server"> /// A <see cref="DatabaseServer"/> representing the target server. /// </param> /// <param name="kubeNamespace"> /// An optional target Kubernetes namespace. /// </param> /// <returns> /// The configured <see cref="PrometheusServiceMonitorV1"/>. /// </returns> public PrometheusServiceMonitorV1 ServiceMonitor(DatabaseServer server, string kubeNamespace = null) { if (server == null) { throw new ArgumentNullException(nameof(server)); } string baseName = Names.BaseName(server); return(ServiceMonitor( name: $"{baseName}-monitor", kubeNamespace: kubeNamespace, spec: Specs.ServiceMonitor(server), labels: new Dictionary <string, string> { ["k8s-app"] = baseName, ["cloud.dimensiondata.daas.server-id"] = server.Id, ["cloud.dimensiondata.daas.monitor-type"] = "database-server" } )); }
/// <summary> /// Create a new <see cref="SecretV1"/> for the specified database server's credentials. /// </summary> /// <param name="server"> /// A <see cref="DatabaseServer"/> representing the target server. /// </param> /// <param name="serverCertificate"> /// <see cref="CertificateCredentials"/> representing the server certificate. /// </param> /// <param name="kubeNamespace"> /// An optional target Kubernetes namespace. /// </param> /// <returns> /// The configured <see cref="SecretV1"/>. /// </returns> public SecretV1 CredentialsSecret(DatabaseServer server, CertificateCredentials serverCertificate, string kubeNamespace = null) { if (server == null) { throw new ArgumentNullException(nameof(server)); } if (serverCertificate == null) { throw new ArgumentNullException(nameof(serverCertificate)); } var secretData = new Dictionary <string, string>(); if (!String.IsNullOrWhiteSpace(serverCertificate.IssuingCACertificateContent)) { secretData["ca.crt"] = Convert.ToBase64String( Encoding.ASCII.GetBytes(serverCertificate.IssuingCACertificateContent) ); } secretData["server.crt"] = Convert.ToBase64String( Encoding.ASCII.GetBytes(serverCertificate.CertificateContent) ); secretData["server.key"] = Convert.ToBase64String( Encoding.ASCII.GetBytes(serverCertificate.PrivateKey) ); string pfxPassword; byte[] passwordBytes = new byte[16]; using (RandomNumberGenerator random = RandomNumberGenerator.Create()) { random.GetBytes(passwordBytes); } pfxPassword = Convert.ToBase64String(passwordBytes); secretData["server.pfx"] = Convert.ToBase64String( serverCertificate.ToPfx(pfxPassword) ); secretData["pfx-password"] = Convert.ToBase64String( Encoding.ASCII.GetBytes(pfxPassword) ); if (server.Settings is SqlServerSettings sqlServerSettings && !String.IsNullOrWhiteSpace(sqlServerSettings.AdminPassword)) { secretData["sa-password"] = Convert.ToBase64String( Encoding.ASCII.GetBytes(sqlServerSettings.AdminPassword) ); } return(CredentialsSecret( name: Names.CredentialsSecret(server), kubeNamespace: kubeNamespace, data: secretData, labels: new Dictionary <string, string> { ["k8s-app"] = Names.BaseName(server), ["cloud.dimensiondata.daas.server-id"] = server.Id, ["cloud.dimensiondata.daas.secret-type"] = "credentials" } )); }
/// <summary> /// Build a <see cref="DeploymentSpecV1Beta1"/> for the specified server. /// </summary> /// <param name="server"> /// A <see cref="DatabaseServer"/> representing the target server. /// </param> /// <returns> /// The configured <see cref="DeploymentSpecV1Beta1"/>. /// </returns> public DeploymentSpecV1Beta1 Deployment(DatabaseServer server) { if (server == null) { throw new ArgumentNullException(nameof(server)); } string baseName = Names.BaseName(server); var deploymentSpec = new DeploymentSpecV1Beta1 { Replicas = 1, MinReadySeconds = 30, Strategy = new DeploymentStrategyV1Beta1 { Type = "Recreate" // Shut down the old instance before starting the new one }, Selector = new LabelSelectorV1 { MatchLabels = new Dictionary <string, string> { ["k8s-app"] = baseName } }, Template = new PodTemplateSpecV1 { Metadata = new ObjectMetaV1 { Labels = new Dictionary <string, string> { ["k8s-app"] = baseName, ["cloud.dimensiondata.daas.server-id"] = server.Id, ["cloud.dimensiondata.daas.server-kind"] = server.Kind.ToString() } }, Spec = new PodSpecV1 { TerminationGracePeriodSeconds = 60, ImagePullSecrets = new List <LocalObjectReferenceV1> { new LocalObjectReferenceV1 { Name = "daas-registry" } }, Containers = new List <ContainerV1>(), Volumes = new List <VolumeV1> { new VolumeV1 { Name = "data", PersistentVolumeClaim = new PersistentVolumeClaimVolumeSourceV1 { ClaimName = Names.DataVolumeClaim(server) } } } } } }; PodSpecV1 podSpec = deploymentSpec.Template.Spec; switch (server.Kind) { case DatabaseServerKind.SqlServer: { // SQL Server podSpec.Containers.Add(new ContainerV1 { Name = "sql-server", Image = ProvisioningOptions.Images.SQL, Resources = new ResourceRequirementsV1 { Requests = new Dictionary <string, string> { ["memory"] = "4Gi" // SQL Server for Linux requires at least 4 GB of RAM }, Limits = new Dictionary <string, string> { ["memory"] = "6Gi" // If you're using more than 6 GB of RAM, then you should probably host stand-alone } }, Env = new List <EnvVarV1> { new EnvVarV1 { Name = "ACCEPT_EULA", Value = "Y" }, new EnvVarV1 { Name = "SA_PASSWORD", ValueFrom = new EnvVarSourceV1 { SecretKeyRef = new SecretKeySelectorV1 { Name = Names.CredentialsSecret(server), Key = "sa-password" } } } }, Ports = new List <ContainerPortV1> { new ContainerPortV1 { ContainerPort = 1433 } }, VolumeMounts = new List <VolumeMountV1> { new VolumeMountV1 { Name = "data", SubPath = baseName, MountPath = "/var/opt/mssql" } } }); // Prometheus exporter podSpec.Containers.Add(new ContainerV1 { Name = "prometheus-exporter", Image = ProvisioningOptions.Images.SQLExporter, Env = new List <EnvVarV1> { new EnvVarV1 { Name = "SERVER", Value = "127.0.0.1", }, new EnvVarV1 { Name = "USERNAME", Value = "sa" }, new EnvVarV1 { Name = "PASSWORD", ValueFrom = new EnvVarSourceV1 { SecretKeyRef = new SecretKeySelectorV1 { Name = Names.CredentialsSecret(server), Key = "sa-password" } } }, new EnvVarV1 { Name = "DEBUG", Value = "app" } }, Ports = new List <ContainerPortV1> { new ContainerPortV1 { ContainerPort = 4000 } } }); break; } case DatabaseServerKind.RavenDB: { podSpec.Containers.Add(new ContainerV1 { Name = "ravendb", Image = ProvisioningOptions.Images.RavenDB, Resources = new ResourceRequirementsV1 { Requests = new Dictionary <string, string> { ["memory"] = "1Gi" }, Limits = new Dictionary <string, string> { ["memory"] = "3Gi" } }, Env = new List <EnvVarV1> { new EnvVarV1 { Name = "UNSECURED_ACCESS_ALLOWED", Value = "PublicNetwork" } }, Ports = new List <ContainerPortV1> { new ContainerPortV1 { Name = "http", ContainerPort = 8080 }, new ContainerPortV1 { Name = "tcp", ContainerPort = 38888 } }, VolumeMounts = new List <VolumeMountV1> { new VolumeMountV1 { Name = "data", SubPath = baseName, MountPath = "/databases" } } }); break; } default: { throw new NotSupportedException($"Unsupported server kind ({server.Kind})."); } } return(deploymentSpec); }