public DevOpsInfra() { _config = new Config("ado"); #pragma warning disable Pulumi.InputMap <string> tags = JsonConvert.DeserializeObject <Dictionary <string, string> >(_config.RequireObject <JsonElement>("tags").ToString()); #region AzureNative.Resources.ResourceGroup (-rg) var resourceGroup = new Pulumi.AzureNative.Resources.ResourceGroup($"{_config.Require("resourceGroup")}-{_config.Require("env")}-rg-", new Pulumi.AzureNative.Resources.ResourceGroupArgs { Location = _config.Require("location"), Tags = tags, }); this.ResourceGroupName = resourceGroup.Name; #endregion #region NetworkSecurityGroup (-nsg) // NSGs can be connected to Subnets or Network cards (NICs). // When applied to the subnet, the NSG applies to all VMs on that subnet. If you apply only to the NIC card, just that one VM is affected. string myIp = new System.Net.WebClient().DownloadString("https://api.ipify.org"); var networkSecurityGroup = new Pulumi.AzureNative.Network.NetworkSecurityGroup($"{_config.Require("vnet.name")}-{_config.Require("env")}-nsg-", new Pulumi.AzureNative.Network.NetworkSecurityGroupArgs { ResourceGroupName = resourceGroup.Name, SecurityRules = { new Pulumi.AzureNative.Network.Inputs.SecurityRuleArgs { Name = "allow-rdp", Protocol = SecurityRuleProtocol.Tcp, SourcePortRange = "*", DestinationPortRange = "3389", SourceAddressPrefix = myIp, DestinationAddressPrefix = "*", Access = "Allow", Priority = 300, Direction = "Inbound", }, new Pulumi.AzureNative.Network.Inputs.SecurityRuleArgs { Name = "allow-http", // _config.Require("nsg.name"), Protocol = SecurityRuleProtocol.Tcp, SourcePortRange = "*", DestinationPortRange = "443", SourceAddressPrefix = "*", DestinationAddressPrefix = "*", Access = "Allow", Priority = 301, Direction = "Inbound", }, }, }); #endregion #region VirtualNetwork (-vnet) var network = new VirtualNetwork($"{_config.Require("vnet.name")}-{_config.Require("env")}-vnet-", new VirtualNetworkArgs { AddressSpace = new Pulumi.AzureNative.Network.Inputs.AddressSpaceArgs { AddressPrefixes = { _config.Require("vnet.cidr"), }, }, Location = _config.Require("location"), ResourceGroupName = resourceGroup.Name, Subnets = { new Pulumi.AzureNative.Network.Inputs.SubnetArgs { AddressPrefix = _config.Require("bastion.cidr"), Name = "AzureBastionSubnet", }, new Pulumi.AzureNative.Network.Inputs.SubnetArgs { AddressPrefix = _config.Require("snet.cidr"), Name = _config.Require("snet.name"), NetworkSecurityGroup = new Pulumi.AzureNative.Network.Inputs.NetworkSecurityGroupArgs { Id = networkSecurityGroup.Id, }, }, }, VirtualNetworkName = "AzureDevOps", // VirtualNetworkPeerings EnableDdosProtection = false, } ); #endregion #region PublicIp (-ip) var publicIp = new Pulumi.AzureNative.Network.PublicIPAddress($"bastion-{_config.Require("env")}-ip-", new Pulumi.AzureNative.Network.PublicIPAddressArgs { PublicIPAddressVersion = IPVersion.IPv4, PublicIPAllocationMethod = IPAllocationMethod.Static, IdleTimeoutInMinutes = 4, Location = _config.Require("location"), ResourceGroupName = resourceGroup.Name, Sku = new Pulumi.AzureNative.Network.Inputs.PublicIPAddressSkuArgs { Name = PublicIPAddressSkuName.Standard, Tier = PublicIPAddressSkuTier.Regional, }, }); var vmPublicIp = new Pulumi.AzureNative.Network.PublicIPAddress($"{_config.Require("agent.name")}-{_config.Require("env")}-ip-", new Pulumi.AzureNative.Network.PublicIPAddressArgs { PublicIPAddressVersion = IPVersion.IPv4, PublicIPAllocationMethod = IPAllocationMethod.Static, IdleTimeoutInMinutes = 4, Location = _config.Require("location"), ResourceGroupName = resourceGroup.Name, Sku = new Pulumi.AzureNative.Network.Inputs.PublicIPAddressSkuArgs { Name = PublicIPAddressSkuName.Standard, Tier = PublicIPAddressSkuTier.Regional, }, }); #endregion #region NetworkInterface (-nic) var networkInterface = new Pulumi.AzureNative.Network.NetworkInterface($"{_config.Require("agent.name")}-{_config.Require("env")}-nic-", new Pulumi.AzureNative.Network.NetworkInterfaceArgs { // AdoAgent has size Standard_B2s, which is not compatible with accelerated networking on network interface. // https://docs.microsoft.com/en-us/azure/virtual-network/create-vm-accelerated-networking-powershell // EnableAcceleratedNetworking = true, IpConfigurations = { new Pulumi.AzureNative.Network.Inputs.NetworkInterfaceIPConfigurationArgs { Name = "ipconfig1", Primary = true, PrivateIPAllocationMethod = IPAllocationMethod.Dynamic, PrivateIPAddressVersion = "IPv4", PublicIPAddress = new Pulumi.AzureNative.Network.Inputs.PublicIPAddressArgs { Id = vmPublicIp.Id, }, Subnet = new Pulumi.AzureNative.Network.Inputs.SubnetArgs { #pragma warning disable Id = network.Subnets.Apply(s => s[1].Id), }, }, }, Location = _config.Require("location"), ResourceGroupName = resourceGroup.Name, }); #endregion #region VirtualMachine (-vm) var vm = new Pulumi.AzureNative.Compute.VirtualMachine($"{_config.Require("agent.name")}-{_config.Require("env")}-vmi-", new Pulumi.AzureNative.Compute.VirtualMachineArgs { ResourceGroupName = resourceGroup.Name, HardwareProfile = new Pulumi.AzureNative.Compute.Inputs.HardwareProfileArgs { VmSize = _config.Require("vm.vmSize") }, VmName = "AdoAgent", DiagnosticsProfile = new Pulumi.AzureNative.Compute.Inputs.DiagnosticsProfileArgs { BootDiagnostics = new Pulumi.AzureNative.Compute.Inputs.BootDiagnosticsArgs { Enabled = true } }, NetworkProfile = new Pulumi.AzureNative.Compute.Inputs.NetworkProfileArgs { NetworkInterfaces = { new Pulumi.AzureNative.Compute.Inputs.NetworkInterfaceReferenceArgs { Id = networkInterface.Id, Primary = true, }, } }, StorageProfile = new Pulumi.AzureNative.Compute.Inputs.StorageProfileArgs { ImageReference = new Pulumi.AzureNative.Compute.Inputs.ImageReferenceArgs { Publisher = "MicrosoftWindowsServer", Offer = "WindowsServer", Sku = "2019-Datacenter-with-Containers-smalldisk", Version = "latest" }, OsDisk = new Pulumi.AzureNative.Compute.Inputs.OSDiskArgs { OsType = Pulumi.AzureNative.Compute.OperatingSystemTypes.Windows, Name = "system", CreateOption = Pulumi.AzureNative.Compute.DiskCreateOptionTypes.FromImage, Caching = Pulumi.AzureNative.Compute.CachingTypes.ReadWrite, ManagedDisk = new Pulumi.AzureNative.Compute.Inputs.ManagedDiskParametersArgs { StorageAccountType = Pulumi.AzureNative.Compute.StorageAccountTypes.Standard_LRS, }, DiskSizeGB = 100, }, DataDisks = { new Pulumi.AzureNative.Compute.Inputs.DataDiskArgs { Name = "Data", CreateOption = Pulumi.AzureNative.Compute.DiskCreateOptionTypes.Empty, DiskSizeGB = _config.RequireInt32("vm.diskSizeGB"), Lun = 0, Caching = Pulumi.AzureNative.Compute.CachingTypes.None, }, }, }, OsProfile = new Pulumi.AzureNative.Compute.Inputs.OSProfileArgs { ComputerName = _config.Require("agent.name"), AdminUsername = _config.Require("admin.user"), AdminPassword = _config.RequireSecret("admin.pw"), WindowsConfiguration = new Pulumi.AzureNative.Compute.Inputs.WindowsConfigurationArgs { ProvisionVMAgent = true, EnableAutomaticUpdates = true, PatchSettings = new Pulumi.AzureNative.Compute.Inputs.PatchSettingsArgs { PatchMode = Pulumi.AzureNative.Compute.WindowsVMGuestPatchMode.AutomaticByOS, EnableHotpatching = false, }, }, }, }); // new CustomResourceOptions { DeleteBeforeReplace = true }); #endregion #region BastionHost (-bas) // Azure Bastion is a fully managed PaaS service that provides secure and seamless RDP and SSH access to virtual machines through the Azure Portal. // A BastionHost is provisioned within a Virtual Network (VNet) to provid SSL access to all VMs in the VNet without exposure through public IP addresses. var bastionHost = new Pulumi.AzureNative.Network.BastionHost($"{_config.Require("vnet.name")}-{_config.Require("env")}-bas-", new Pulumi.AzureNative.Network.BastionHostArgs { Location = resourceGroup.Location, ResourceGroupName = resourceGroup.Name, IpConfigurations = { new Pulumi.AzureNative.Network.Inputs.BastionHostIPConfigurationArgs { Name = "bastionHostIpConfiguration", PublicIPAddress = new Pulumi.AzureNative.Network.Inputs.SubResourceArgs { Id = publicIp.Id, }, Subnet = new Pulumi.AzureNative.Network.Inputs.SubResourceArgs { Id = network.Subnets.Apply(s => s[0].Id), }, }, }, }); #endregion }
private static AzureResourceBag CreateBaseAzureInfrastructure(Config config) { var location = config.Require("azure-location"); var environment = config.Require("azure-tags-environment"); var owner = config.Require("azure-tags-owner"); var createdBy = config.Require("azure-tags-createdby"); var kubernetesVersion = config.Require("kubernetes-version"); var kubernetesNodeCount = config.RequireInt32("kubernetes-scaling-nodecount"); var sqlUser = config.RequireSecret("azure-sqlserver-username"); var sqlPassword = config.RequireSecret("azure-sqlserver-password"); var tags = new InputMap <string> { { "Environment", environment }, { "CreatedBy", createdBy }, { "Owner", owner } }; var resourceGroup = new ResourceGroup("pet-doctor-resource-group", new ResourceGroupArgs { Name = "pet-doctor", Location = location, Tags = tags }); var vnet = new VirtualNetwork("pet-doctor-vnet", new VirtualNetworkArgs { ResourceGroupName = resourceGroup.Name, Name = "petdoctorvnet", AddressSpaces = { "10.0.0.0/8" }, Tags = tags }); var subnet = new Subnet("pet-doctor-subnet", new SubnetArgs { ResourceGroupName = resourceGroup.Name, Name = "petdoctorsubet", AddressPrefixes = { "10.240.0.0/16" }, VirtualNetworkName = vnet.Name, ServiceEndpoints = new InputList <string> { "Microsoft.KeyVault", "Microsoft.Sql" } }); var registry = new Registry("pet-doctor-acr", new RegistryArgs { ResourceGroupName = resourceGroup.Name, Name = "petdoctoracr", Sku = "Standard", AdminEnabled = true, Tags = tags }); var aksServicePrincipalPassword = new RandomPassword("pet-doctor-aks-ad-sp-password", new RandomPasswordArgs { Length = 20, Special = true, }).Result; var clusterAdApp = new Application("pet-doctor-aks-ad-app", new ApplicationArgs { Name = "petdoctoraks" }); var clusterAdServicePrincipal = new ServicePrincipal("aks-app-sp", new ServicePrincipalArgs { ApplicationId = clusterAdApp.ApplicationId }); var clusterAdServicePrincipalPassword = new ServicePrincipalPassword("aks-app-sp-pwd", new ServicePrincipalPasswordArgs { ServicePrincipalId = clusterAdServicePrincipal.ObjectId, EndDate = "2099-01-01T00:00:00Z", Value = aksServicePrincipalPassword }); // Grant networking permissions to the SP (needed e.g. to provision Load Balancers) var subnetAssignment = new Assignment("pet-doctor-aks-sp-subnet-assignment", new AssignmentArgs { PrincipalId = clusterAdServicePrincipal.Id, RoleDefinitionName = "Network Contributor", Scope = subnet.Id }); var acrAssignment = new Assignment("pet-doctor-aks-sp-acr-assignment", new AssignmentArgs { PrincipalId = clusterAdServicePrincipal.Id, RoleDefinitionName = "AcrPull", Scope = registry.Id }); var logAnalyticsWorkspace = new AnalyticsWorkspace("pet-doctor-aks-log-analytics", new AnalyticsWorkspaceArgs { ResourceGroupName = resourceGroup.Name, Name = "petdoctorloganalytics", Sku = "PerGB2018", Tags = tags }); var logAnalyticsSolution = new AnalyticsSolution("pet-doctor-aks-analytics-solution", new AnalyticsSolutionArgs { ResourceGroupName = resourceGroup.Name, SolutionName = "ContainerInsights", WorkspaceName = logAnalyticsWorkspace.Name, WorkspaceResourceId = logAnalyticsWorkspace.Id, Plan = new AnalyticsSolutionPlanArgs { Product = "OMSGallery/ContainerInsights", Publisher = "Microsoft" } }); var sshPublicKey = new PrivateKey("ssh-key", new PrivateKeyArgs { Algorithm = "RSA", RsaBits = 4096, }); var cluster = new KubernetesCluster("pet-doctor-aks", new KubernetesClusterArgs { ResourceGroupName = resourceGroup.Name, Name = "petdoctoraks", DnsPrefix = "dns", KubernetesVersion = kubernetesVersion, DefaultNodePool = new KubernetesClusterDefaultNodePoolArgs { Name = "aksagentpool", NodeCount = kubernetesNodeCount, VmSize = "Standard_D2_v2", OsDiskSizeGb = 30, VnetSubnetId = subnet.Id }, LinuxProfile = new KubernetesClusterLinuxProfileArgs { AdminUsername = "******", SshKey = new KubernetesClusterLinuxProfileSshKeyArgs { KeyData = sshPublicKey.PublicKeyOpenssh } }, ServicePrincipal = new KubernetesClusterServicePrincipalArgs { ClientId = clusterAdApp.ApplicationId, ClientSecret = clusterAdServicePrincipalPassword.Value }, RoleBasedAccessControl = new KubernetesClusterRoleBasedAccessControlArgs { Enabled = true }, NetworkProfile = new KubernetesClusterNetworkProfileArgs { NetworkPlugin = "azure", ServiceCidr = "10.2.0.0/24", DnsServiceIp = "10.2.0.10", DockerBridgeCidr = "172.17.0.1/16" }, AddonProfile = new KubernetesClusterAddonProfileArgs { OmsAgent = new KubernetesClusterAddonProfileOmsAgentArgs { Enabled = true, LogAnalyticsWorkspaceId = logAnalyticsWorkspace.Id } }, Tags = tags }); var sqlServer = new SqlServer("pet-doctor-sql", new SqlServerArgs { ResourceGroupName = resourceGroup.Name, Name = "petdoctorsql", Tags = tags, Version = "12.0", AdministratorLogin = sqlUser, AdministratorLoginPassword = sqlPassword }); var sqlvnetrule = new VirtualNetworkRule("pet-doctor-sql", new VirtualNetworkRuleArgs { ResourceGroupName = resourceGroup.Name, Name = "petdoctorsql", ServerName = sqlServer.Name, SubnetId = subnet.Id, }); var appInsights = new Insights("pet-doctor-ai", new InsightsArgs { ApplicationType = "web", Name = "petdoctor", ResourceGroupName = resourceGroup.Name, Tags = tags }); var provider = new Provider("pet-doctor-aks-provider", new ProviderArgs { KubeConfig = cluster.KubeConfigRaw }); return(new AzureResourceBag { ResourceGroup = resourceGroup, SqlServer = sqlServer, Cluster = cluster, ClusterProvider = provider, AppInsights = appInsights, AksServicePrincipal = clusterAdServicePrincipal, Subnet = subnet, Registry = registry, Tags = tags }); }