private static AppointmentApiAzureResourceBag CreateAppointmentApiAzureResources(AzureResourceBag azureResources, Config config, Input <string> registryImageName) { var tenantId = config.Require("azure-tenantid"); var appointmentApiDb = new Database("appointments-api-db", new DatabaseArgs { ResourceGroupName = azureResources.ResourceGroup.Name, Name = "appointment-api", ServerName = azureResources.SqlServer.Name, RequestedServiceObjectiveName = "S0", Tags = azureResources.Tags }); var image = new Image("appointments-api-docker-image", new ImageArgs { Build = $".{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}..{Path.DirectorySeparatorChar}", Registry = new ImageRegistry { Server = azureResources.Registry.LoginServer, Username = azureResources.Registry.AdminUsername, Password = azureResources.Registry.AdminPassword }, ImageName = registryImageName }, new ComponentResourceOptions { DependsOn = new InputList <Resource> { azureResources.Registry } }); var appointmentApiIdentity = new UserAssignedIdentity("appointments-api", new UserAssignedIdentityArgs { ResourceGroupName = azureResources.ResourceGroup.Name, Name = "appointments-api", Tags = azureResources.Tags }); // AKS service principal needs to have Managed Identity Operator rights over the user assigned identity else AAD pod identity won't work var aksSpAppointmentApiAccessPolicy = new Assignment("aks-sp-appontment-api-access", new AssignmentArgs { PrincipalId = azureResources.AksServicePrincipal.ObjectId, RoleDefinitionName = "Managed Identity Operator", Scope = appointmentApiIdentity.Id }); var sqlAdmin = new ActiveDirectoryAdministrator("appointments-api-sql-access", new ActiveDirectoryAdministratorArgs { ResourceGroupName = azureResources.ResourceGroup.Name, TenantId = tenantId, ObjectId = appointmentApiIdentity.PrincipalId, Login = "******", ServerName = azureResources.SqlServer.Name }); var clientConfig = Output.Create(GetClientConfig.InvokeAsync()); var currentPrincipalTenantId = clientConfig.Apply(c => c.TenantId); var currentPrincipal = clientConfig.Apply(c => c.ObjectId); var appointmentApiKeyVault = new KeyVault("appointment-api-keyvault", new KeyVaultArgs { ResourceGroupName = azureResources.ResourceGroup.Name, Name = "appointments-api", EnabledForDiskEncryption = true, TenantId = tenantId, SkuName = "standard", AccessPolicies = new InputList <KeyVaultAccessPolicyArgs> { new KeyVaultAccessPolicyArgs { TenantId = tenantId, ObjectId = azureResources.AksServicePrincipal.ObjectId, SecretPermissions = new[] { "get", "list" } }, new KeyVaultAccessPolicyArgs { TenantId = tenantId, ObjectId = appointmentApiIdentity.PrincipalId, SecretPermissions = new[] { "get", "list" } }, new KeyVaultAccessPolicyArgs { TenantId = currentPrincipalTenantId, ObjectId = currentPrincipal, SecretPermissions = { "delete", "get", "list", "set" }, } }, NetworkAcls = new KeyVaultNetworkAclsArgs { DefaultAction = "Deny", Bypass = "******", VirtualNetworkSubnetIds = new InputList <string> { azureResources.Subnet.Id }, // Need to whitelist the local public IP address otherwise setting secrets will fail IpRules = new InputList <string> { GetMyPublicIpAddress() } }, Tags = azureResources.Tags }); var secret = new Pulumi.Azure.KeyVault.Secret("appointments-api-db-connection-string", new Pulumi.Azure.KeyVault.SecretArgs { KeyVaultId = appointmentApiKeyVault.Id, Name = "ConnectionStrings--PetDoctorContext", Value = Output.Tuple(azureResources.SqlServer.Name, azureResources.SqlServer.Name, azureResources.SqlServer.AdministratorLogin, azureResources.SqlServer.AdministratorLoginPassword).Apply( t => { var(server, database, administratorLogin, administratorLoginPassword) = t; return ($"Server=tcp:{server}.database.windows.net;Database={database};User ID={administratorLogin};Password={administratorLoginPassword}"); }) }, new CustomResourceOptions { DependsOn = new InputList <Resource> { azureResources.SqlServer, appointmentApiKeyVault } }); return(new AppointmentApiAzureResourceBag { Identity = appointmentApiIdentity, KeyVault = appointmentApiKeyVault }); }
private static void SetupAppointmentApiInKubernetes(AzureResourceBag azureResources, AppointmentApiAzureResourceBag appointmentApiAzureResources, PetDoctorClusterOptions clusterOptions) { var customOpts = new CustomResourceOptions { DependsOn = azureResources.Cluster, Provider = azureResources.ClusterProvider }; var secret = new Secret(clusterOptions.AppointmentApi.SecretName, new SecretArgs { Metadata = new ObjectMetaArgs { Namespace = clusterOptions.Namespace, Name = clusterOptions.AppointmentApi.SecretName }, Kind = "Secret", ApiVersion = "v1", Type = "Opaque", Data = new InputMap <string> { { "keyvault-url", appointmentApiAzureResources.KeyVault.VaultUri.Apply(kvUrl => Convert.ToBase64String(Encoding.UTF8.GetBytes(kvUrl))) }, { "appinsights-instrumentationkey", azureResources.AppInsights.InstrumentationKey.Apply(key => Convert.ToBase64String(Encoding.UTF8.GetBytes(key))) } } }, customOpts); var appointmentApiPodIdentity = new CustomResource(clusterOptions.AppointmentApi.AadPodIdentityName, new AzureIdentityResourceArgs { Metadata = new ObjectMetaArgs { Name = clusterOptions.AppointmentApi.AadPodIdentityName }, Spec = new AzureIdentitySpecArgs { Type = 0, ResourceId = appointmentApiAzureResources.Identity.Id, ClientId = appointmentApiAzureResources.Identity.ClientId } }, customOpts); var appointmentApiPodIdentityBinding = new CustomResource(clusterOptions.AppointmentApi.AadPodIdentityBindingName, new AzureIdentityBindingResourceArgs { Metadata = new ObjectMetaArgs { Name = clusterOptions.AppointmentApi.AadPodIdentityBindingName }, Spec = new AzureIdentityBindingSpecArgs { AzureIdentity = clusterOptions.AppointmentApi.AadPodIdentityName, Selector = clusterOptions.AppointmentApi.AadPodIdentitySelector } }, new CustomResourceOptions { DependsOn = new InputList <Resource> { azureResources.Cluster, appointmentApiAzureResources.Identity }, Provider = azureResources.ClusterProvider }); var appointmentApiDeployment = new Deployment("appointment-api-deployment", new DeploymentArgs { ApiVersion = "apps/v1beta1", Kind = "Deployment", Metadata = new ObjectMetaArgs { Name = clusterOptions.AppointmentApi.DeploymentName, Namespace = clusterOptions.Namespace, Labels = new InputMap <string> { { "app", clusterOptions.AppointmentApi.DeploymentName }, { "aadpodidbinding", clusterOptions.AppointmentApi.AadPodIdentitySelector } } }, Spec = new DeploymentSpecArgs { Replicas = clusterOptions.AppointmentApi.ReplicaCount, Selector = new LabelSelectorArgs { MatchLabels = new InputMap <string> { { "app", clusterOptions.AppointmentApi.DeploymentName } } }, Template = new PodTemplateSpecArgs { Metadata = new ObjectMetaArgs { Labels = new InputMap <string> { { "app", clusterOptions.AppointmentApi.DeploymentName }, { "aadpodidbinding", clusterOptions.AppointmentApi.AadPodIdentitySelector } } }, Spec = new PodSpecArgs { Containers = new InputList <ContainerArgs> { new ContainerArgs { Name = clusterOptions.AppointmentApi.DeploymentName, Image = clusterOptions.AppointmentApi.Image, Ports = new InputList <ContainerPortArgs> { new ContainerPortArgs { ContainerPortValue = clusterOptions.AppointmentApi.Port, Protocol = "TCP" } }, ReadinessProbe = new ProbeArgs { HttpGet = new HTTPGetActionArgs { Port = clusterOptions.AppointmentApi.Port, Path = "/ready", Scheme = "HTTP" }, InitialDelaySeconds = 15, TimeoutSeconds = 2, PeriodSeconds = 10, FailureThreshold = 3 }, LivenessProbe = new ProbeArgs { HttpGet = new HTTPGetActionArgs { Port = clusterOptions.AppointmentApi.Port, Path = "/live", Scheme = "HTTP" }, InitialDelaySeconds = 30, TimeoutSeconds = 2, PeriodSeconds = 5, FailureThreshold = 3 }, Env = new InputList <EnvVarArgs> { new EnvVarArgs { Name = "ASPNETCORE_ENVIRONMENT", Value = "Production" }, new EnvVarArgs { Name = "KEYVAULT__URL", ValueFrom = new EnvVarSourceArgs { SecretKeyRef = new SecretKeySelectorArgs { Name = clusterOptions.AppointmentApi.SecretName, Key = "keyvault-url" } } }, new EnvVarArgs { Name = "APPLICATIONINSIGHTS__INSTRUMENTATIONKEY", ValueFrom = new EnvVarSourceArgs { SecretKeyRef = new SecretKeySelectorArgs { Name = clusterOptions.AppointmentApi.SecretName, Key = "appinsights-instrumentationkey" } } }, new EnvVarArgs { Name = "HOSTENV", Value = "K8S" }, new EnvVarArgs { Name = "PATH_BASE", Value = "api" } } } } } } } }, new CustomResourceOptions { DependsOn = new InputList <Resource> { azureResources.Cluster, appointmentApiAzureResources.Identity, appointmentApiPodIdentityBinding }, Provider = azureResources.ClusterProvider }); var appointmentApiService = new Service(clusterOptions.AppointmentApi.ServiceName, new ServiceArgs { ApiVersion = "v1", Kind = "Service", Metadata = new ObjectMetaArgs { Name = clusterOptions.AppointmentApi.ServiceName, Namespace = clusterOptions.Namespace }, Spec = new ServiceSpecArgs { Selector = new InputMap <string> { { "app", clusterOptions.AppointmentApi.DeploymentName } }, Type = "ClusterIP", ClusterIP = "None", Ports = new InputList <ServicePortArgs> { new ServicePortArgs { Name = "http", Protocol = "TCP", Port = 80, TargetPort = clusterOptions.AppointmentApi.Port } } } }, customOpts); var appointmentApiIngress = new Ingress(clusterOptions.AppointmentApi.IngressName, new IngressArgs { ApiVersion = "extensions/v1beta1", Kind = "Ingress", Metadata = new ObjectMetaArgs { Name = clusterOptions.AppointmentApi.IngressName, Namespace = clusterOptions.Namespace, Annotations = new InputMap <string> { { "kubernetes.io/ingress.class", "nginx" }, { "certmanager.k8s.io/cluster-issuer", "letsencrypt-prod" } } }, Spec = new IngressSpecArgs { Tls = new InputList <IngressTLSArgs> { new IngressTLSArgs { Hosts = new InputList <string> { clusterOptions.Domain }, SecretName = "tls-secret" } }, Rules = new InputList <IngressRuleArgs> { new IngressRuleArgs { Host = clusterOptions.Domain, Http = new HTTPIngressRuleValueArgs { Paths = new InputList <HTTPIngressPathArgs> { new HTTPIngressPathArgs { Path = "/api", Backend = new IngressBackendArgs { ServiceName = clusterOptions.AppointmentApi.ServiceName, ServicePort = clusterOptions.AppointmentApi.Port } } } } } } } }, customOpts); }
private static void ConfigureKubernetesCluster(AzureResourceBag azureResources, PetDoctorClusterOptions clusterOptions) { var componentOpts = new ComponentResourceOptions { DependsOn = azureResources.Cluster, Provider = azureResources.ClusterProvider }; var customOpts = new CustomResourceOptions { DependsOn = azureResources.Cluster, Provider = azureResources.ClusterProvider }; var aadPodIdentityDeployment = new ConfigFile("k8s-aad-pod-identity", new ConfigFileArgs { File = "https://raw.githubusercontent.com/Azure/aad-pod-identity/master/deploy/infra/deployment-rbac.yaml" }, componentOpts); var certManagerDeployment = new ConfigFile("k8s-cert-manager", new ConfigFileArgs { File = "https://raw.githubusercontent.com/jetstack/cert-manager/release-0.8/deploy/manifests/00-crds.yaml" }, componentOpts); var nginxDeployment = new ConfigFile("k8s-nginx-ingress", new ConfigFileArgs { File = "https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.34.1/deploy/static/provider/cloud/deploy.yaml" }, componentOpts); var clusterNamespace = new Namespace("k8s-namespace", new NamespaceArgs { Metadata = new ObjectMetaArgs { Name = clusterOptions.Namespace } }, customOpts); var clusterIssuer = new CustomResource("k8s-cert-manager-cluster-issuer", new CertManagerClusterIssuerResourceArgs { Metadata = new ObjectMetaArgs { Name = "letsencrypt-prod" }, Spec = new CertManagerClusterIssuerSpecArgs { Acme = new CertManagerClusterIssuerAcmeArgs { Email = clusterOptions.CertificateIssuerAcmeEmail, Server = "https://acme-v02.api.letsencrypt.org/directory", PrivateKeySecretRef = new CertManagerClusterIssuerAcmeSecretArgs { Name = "letsencrypt-prod" } } } }, customOpts); var certs = new CustomResource("k8s-cert-manager-domain-cert", new CertManagerCertificateResourceArgs { Metadata = new ObjectMetaArgs { Name = "tls-secret" }, Spec = new CertManagerCertificateSpecArgs { SecretName = "tls-secret", DnsNames = clusterOptions.Domain, Acme = new CertManagerCertificateAcmeArgs { Config = new CertManagerCertificateAcmeConfigArgs { Http = new CertManagerCertificateAcmeConfigHttpArgs { IngressClass = "nginx" }, Domains = clusterOptions.Domain } }, IssuerRef = new CertManagerCertificateIssuerRefArgs { Name = "letsencrypt-prod", Kind = "ClusterIssuer" } } }, customOpts); }