File: AppHost.cs
Web Access
Project: src\playground\CertManagerDemo\CertManagerDemo.AppHost\CertManagerDemo.AppHost.csproj (CertManagerDemo.AppHost)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
#pragma warning disable ASPIREAZURE003 // AddSubnet / AzureSubnetResource are evaluation-only
 
var builder = DistributedApplication.CreateBuilder(args);
 
// VNet layout (mirrors AksDemo so the two playgrounds can be diffed cleanly):
//   10.100.0.0/16   - vnet (chosen to avoid the AKS default service CIDR 10.0.0.0/16)
//     10.100.0.0/22 - aks node pool subnet (1024 IPs - room for pods/nodes)
//     10.100.4.0/24 - public AGC frontend subnet (delegated to ServiceNetworking by AddLoadBalancer)
//     10.100.5.0/24 - admin AGC frontend subnet
var vnet = builder.AddAzureVirtualNetwork("vnet", "10.100.0.0/16");
var aksSubnet = vnet.AddSubnet("aks-nodes", "10.100.0.0/22");
var publicSubnet = vnet.AddSubnet("alb-public", "10.100.4.0/24");
var adminSubnet = vnet.AddSubnet("alb-admin", "10.100.5.0/24");
 
var aks = builder.AddAzureKubernetesEnvironment("aks")
                 .WithSubnet(aksSubnet)
                 .WithSystemNodePool("Standard_D2as_v5");
 
aks.AddNodePool("workload", "Standard_D2as_v5", minCount: 1, maxCount: 3);
 
var publicLb = aks.AddLoadBalancer("public", publicSubnet);
var adminLb = aks.AddLoadBalancer("admin", adminSubnet);
 
// Email used to register the ACME account with Let's Encrypt. Treat as a parameter so
// it can be supplied per-environment (`aspire deploy -p acme-email=...`) without burning
// it into source.
var acmeEmail = builder.AddParameter("acme-email");
 
// Install cert-manager via the typed API. This is the only difference from AksDemo:
// AksDemo wires the chart in by hand and pairs it with a manual cluster-issuer annotation,
// whereas here cert-manager and its ClusterIssuer are first-class resources in the model.
var certManager = aks.AddCertManager("cert-manager");
 
// A single Let's Encrypt production ClusterIssuer with an HTTP-01 solver. cert-manager
// satisfies the challenge by serving a token at /.well-known/acme-challenge/{token} on
// the same hostname being validated, which works for any AGC-assigned FQDN because port
// 80 is publicly reachable.
var letsEncrypt = certManager.AddIssuer("letsencrypt-prod")
                             .WithLetsEncryptProduction(acmeEmail)
                             .WithHttp01Solver();
 
var api = builder.AddProject<Projects.CertManagerDemo_ApiService>("api")
   .WithExternalHttpEndpoints();
 
// Public gateway: serves /api -> the api service, attached to the public AGC ALB.
// WithTls(letsEncrypt) creates an HTTPS listener AND adds the
// `cert-manager.io/cluster-issuer: letsencrypt-prod` annotation in one call. Once AGC
// assigns the gateway its <random>.fz<n>.alb.azure.com FQDN, the tls-fqdn-discovery
// pipeline step patches it into the listener and cert-manager issues a real Let's Encrypt
// cert via HTTP-01 against that FQDN. No ClusterIssuer needs to be created out-of-band.
aks.AddGateway("storefront-gw")
   .WithLoadBalancer(publicLb)
   .WithRoute("/api", api.GetEndpoint("http"))
   .WithTls(letsEncrypt);
 
// Admin gateway: same backend on a separate AGC ALB. No TLS here on purpose, so the diff
// between an HTTP-only and a cert-manager-managed HTTPS gateway is obvious side-by-side.
aks.AddGateway("admin-gw")
   .WithLoadBalancer(adminLb)
   .WithRoute("/admin", api.GetEndpoint("http"));
 
builder.Build().Run();