├── .gitignore ├── README.md ├── aad-pod-identity ├── README.md └── pod.yaml ├── aks-auto-scaler ├── README.md ├── deploy.json ├── deploy.sh └── deployment.yaml ├── aks-kubenet ├── README.md ├── deploy-group.ps1 ├── deploy.json └── service.yaml ├── aks-rbac └── article.md ├── aks-vnet-arm ├── README.md ├── deploy-group.ps1 ├── deploy.json └── service.yaml ├── custom-logs ├── README.md ├── deploy-multiple-file-to-output.yaml ├── multiple-file-to-host.yaml ├── multiple-file-to-output.yaml ├── single-file-to-output.yaml └── standard-output.yaml ├── dynamic-disks ├── README.md ├── dynamic-with-deploy.yaml └── dynamic-with-stateful.yaml ├── flex-volume ├── README.md └── pod.yaml ├── http-ingress ├── README.md ├── create-cluster.sh ├── domain-name-overload.yaml ├── install-ingress-controller.sh ├── multiple-controller.yaml └── url-based-routing.yaml ├── ingress-multiple-ns ├── README.md ├── ingress1.yaml └── ingress2.yaml ├── kubenet-outbound ├── README.md ├── create-cluster.sh └── deploy.json ├── monitor-metrics ├── README.md ├── create-cluster.sh ├── deploy.json └── service.yaml └── requests-vs-limits ├── CpuRamRequestSolution ├── CpuRamRequestApi │ ├── Controllers │ │ └── ValuesController.cs │ ├── CpuRamRequestApi.csproj │ ├── Program.cs │ ├── Startup.cs │ ├── appsettings.Development.json │ └── appsettings.json ├── CpuRamRequestSolution.sln ├── Dockerfile └── build-container.sh ├── README.md ├── create-cluster.sh ├── deploy.json └── service.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | *.VC.db 100 | *.VC.VC.opendb 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | *.sap 107 | 108 | # Visual Studio Trace Files 109 | *.e2e 110 | 111 | # TFS 2012 Local Workspace 112 | $tf/ 113 | 114 | # Guidance Automation Toolkit 115 | *.gpState 116 | 117 | # ReSharper is a .NET coding add-in 118 | _ReSharper*/ 119 | *.[Rr]e[Ss]harper 120 | *.DotSettings.user 121 | 122 | # JustCode is a .NET coding add-in 123 | .JustCode 124 | 125 | # TeamCity is a build add-in 126 | _TeamCity* 127 | 128 | # DotCover is a Code Coverage Tool 129 | *.dotCover 130 | 131 | # AxoCover is a Code Coverage Tool 132 | .axoCover/* 133 | !.axoCover/settings.json 134 | 135 | # Visual Studio code coverage results 136 | *.coverage 137 | *.coveragexml 138 | 139 | # NCrunch 140 | _NCrunch_* 141 | .*crunch*.local.xml 142 | nCrunchTemp_* 143 | 144 | # MightyMoose 145 | *.mm.* 146 | AutoTest.Net/ 147 | 148 | # Web workbench (sass) 149 | .sass-cache/ 150 | 151 | # Installshield output folder 152 | [Ee]xpress/ 153 | 154 | # DocProject is a documentation generator add-in 155 | DocProject/buildhelp/ 156 | DocProject/Help/*.HxT 157 | DocProject/Help/*.HxC 158 | DocProject/Help/*.hhc 159 | DocProject/Help/*.hhk 160 | DocProject/Help/*.hhp 161 | DocProject/Help/Html2 162 | DocProject/Help/html 163 | 164 | # Click-Once directory 165 | publish/ 166 | 167 | # Publish Web Output 168 | *.[Pp]ublish.xml 169 | *.azurePubxml 170 | # Note: Comment the next line if you want to checkin your web deploy settings, 171 | # but database connection strings (with potential passwords) will be unencrypted 172 | *.pubxml 173 | *.publishproj 174 | 175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 176 | # checkin your Azure Web App publish settings, but sensitive information contained 177 | # in these scripts will be unencrypted 178 | PublishScripts/ 179 | 180 | # NuGet Packages 181 | *.nupkg 182 | # The packages folder can be ignored because of Package Restore 183 | **/[Pp]ackages/* 184 | # except build/, which is used as an MSBuild target. 185 | !**/[Pp]ackages/build/ 186 | # Uncomment if necessary however generally it will be regenerated when needed 187 | #!**/[Pp]ackages/repositories.config 188 | # NuGet v3's project.json files produces more ignorable files 189 | *.nuget.props 190 | *.nuget.targets 191 | 192 | # Microsoft Azure Build Output 193 | csx/ 194 | *.build.csdef 195 | 196 | # Microsoft Azure Emulator 197 | ecf/ 198 | rcf/ 199 | 200 | # Windows Store app package directories and files 201 | AppPackages/ 202 | BundleArtifacts/ 203 | Package.StoreAssociation.xml 204 | _pkginfo.txt 205 | *.appx 206 | 207 | # Visual Studio cache files 208 | # files ending in .cache can be ignored 209 | *.[Cc]ache 210 | # but keep track of directories ending in .cache 211 | !*.[Cc]ache/ 212 | 213 | # Others 214 | ClientBin/ 215 | ~$* 216 | *~ 217 | *.dbmdl 218 | *.dbproj.schemaview 219 | *.jfm 220 | *.pfx 221 | *.publishsettings 222 | orleans.codegen.cs 223 | 224 | # Including strong name files can present a security risk 225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 226 | #*.snk 227 | 228 | # Since there are multiple workflows, uncomment next line to ignore bower_components 229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 230 | #bower_components/ 231 | 232 | # RIA/Silverlight projects 233 | Generated_Code/ 234 | 235 | # Backup & report files from converting an old project file 236 | # to a newer Visual Studio version. Backup files are not needed, 237 | # because we have git ;-) 238 | _UpgradeReport_Files/ 239 | Backup*/ 240 | UpgradeLog*.XML 241 | UpgradeLog*.htm 242 | ServiceFabricBackup/ 243 | *.rptproj.bak 244 | 245 | # SQL Server files 246 | *.mdf 247 | *.ldf 248 | *.ndf 249 | 250 | # Business Intelligence projects 251 | *.rdl.data 252 | *.bim.layout 253 | *.bim_*.settings 254 | *.rptproj.rsuser 255 | 256 | # Microsoft Fakes 257 | FakesAssemblies/ 258 | 259 | # GhostDoc plugin setting file 260 | *.GhostDoc.xml 261 | 262 | # Node.js Tools for Visual Studio 263 | .ntvs_analysis.dat 264 | node_modules/ 265 | 266 | # Visual Studio 6 build log 267 | *.plg 268 | 269 | # Visual Studio 6 workspace options file 270 | *.opt 271 | 272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 273 | *.vbw 274 | 275 | # Visual Studio LightSwitch build output 276 | **/*.HTMLClient/GeneratedArtifacts 277 | **/*.DesktopClient/GeneratedArtifacts 278 | **/*.DesktopClient/ModelManifest.xml 279 | **/*.Server/GeneratedArtifacts 280 | **/*.Server/ModelManifest.xml 281 | _Pvt_Extensions 282 | 283 | # Paket dependency manager 284 | .paket/paket.exe 285 | paket-files/ 286 | 287 | # FAKE - F# Make 288 | .fake/ 289 | 290 | # JetBrains Rider 291 | .idea/ 292 | *.sln.iml 293 | 294 | # CodeRush 295 | .cr/ 296 | 297 | # Python Tools for Visual Studio (PTVS) 298 | __pycache__/ 299 | *.pyc 300 | 301 | # Cake - Uncomment if you are using it 302 | # tools/** 303 | # !tools/packages.config 304 | 305 | # Tabs Studio 306 | *.tss 307 | 308 | # Telerik's JustMock configuration file 309 | *.jmconfig 310 | 311 | # BizTalk build output 312 | *.btp.cs 313 | *.btm.cs 314 | *.odx.cs 315 | *.xsd.cs 316 | 317 | # OpenCover UI analysis results 318 | OpenCover/ 319 | 320 | # Azure Stream Analytics local run output 321 | ASALocalRun/ 322 | 323 | # MSBuild Binary and Structured Log 324 | *.binlog 325 | 326 | # NVidia Nsight GPU debugger configuration file 327 | *.nvuser 328 | 329 | # MFractors (Xamarin productivity tool) working folder 330 | .mfractor/ 331 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aks 2 | 3 | Demos involving Azure Kubernetes Service (AKS) 4 | -------------------------------------------------------------------------------- /aad-pod-identity/README.md: -------------------------------------------------------------------------------- 1 | # Azure AD Pod Identity in AKS 2 | 3 | See the [following article](https://vincentlauzon.com/2019/02/19/azure-ad-pod-identity-in-aks/) for details. 4 | 5 | -------------------------------------------------------------------------------- /aad-pod-identity/pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: test-id-pod 5 | labels: 6 | app: little-pod 7 | aadpodidbinding: little-pod-binding 8 | spec: 9 | containers: 10 | - name: main-container 11 | image: appropriate/curl 12 | args: 13 | - /bin/sh 14 | - -c 15 | - > 16 | while true; 17 | do 18 | sleep 1; 19 | done 20 | -------------------------------------------------------------------------------- /aks-auto-scaler/README.md: -------------------------------------------------------------------------------- 1 | # AKS Auto Scaler 2 | 3 | The following button deploys an AKS cluster inside a VNET with auto-scaler enabled, using Azure ARM Template: 4 | 5 | [![Deploy button](http://azuredeploy.net/deploybutton.png)](https://portal.azure.com/#create/Microsoft.Template/uri/https:%2F%2Fraw.githubusercontent.com%2Fvplauzon%2Faks%2Fmaster%2Faks-auto-scaler%2Fdeploy.json) 6 | 7 | See the [following article](https://vincentlauzon.com/2019/03/20/aks-auto-scaler-with-arm-template/) for details. 8 | -------------------------------------------------------------------------------- /aks-auto-scaler/deploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "DNS Prefix": { 6 | "metadata": { 7 | "description": "DNS Prefix for the cluster" 8 | }, 9 | "type": "string" 10 | }, 11 | "Service Principal App ID": { 12 | "metadata": { 13 | "description": "App ID of the Service Principal" 14 | }, 15 | "type": "string" 16 | }, 17 | "Service Principal Object ID": { 18 | "metadata": { 19 | "description": "Object ID of the Service Principal" 20 | }, 21 | "type": "string" 22 | }, 23 | "Service Principal Secret": { 24 | "metadata": { 25 | "description": "Secret of the Service Principal" 26 | }, 27 | "type": "securestring" 28 | } 29 | }, 30 | "variables": { 31 | "Network Contributor Role": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Authorization/roleDefinitions/', '4d97b98b-1d4f-4787-a291-c67834d212e7')]", 32 | "VNET Name": "cluster-vnet", 33 | "VNET Address Space": "172.16.0.0/18", 34 | "Role Assignment Name": "[concat(variables('VNET Name'), '/Microsoft.Authorization/', guid(concat(resourceGroup().id), variables('Network Contributor Role')))]", 35 | "AKS Subnet Address Space": "172.16.0.0/20", 36 | "Service Subnet Address Space": "172.16.16.0/20", 37 | "Service Cidr": "10.0.0.0/16", 38 | "Dns Service IP": "10.0.0.10", 39 | "Docker Bridge Cidr": "10.2.0.1/16", 40 | "kubernetes version": "1.13.5", 41 | "VM Size": "Standard_B2ms", 42 | "instance count": 3, 43 | "ssh public key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC/DWLnsCzgNo4rXoafDwXRjXBCIyX8m6sPJRVfDSmYgND739wQsfBF/B8RCyU1z+tjmOr+CZYCu6w2FmVL8JDY/aPJC9nDtO5aZSZtAdKJH51PwODsI8E4mthPuC01CxRageEDeEW9u4CCu3HXq6gFBscOEsC1iTYO5gsaxotiGdJS2pYnNHDVTWqhbzi7UPx8xPKJ1M8LKkG2paZLYBHKIhjrxjrAjnnsLkFb/dhfdr9D65Mqf5OGy40X1vQv+rfbLtnpb1DMajlfwQtBQpHY2SnEFbSwQva/l/chyhc4b854Uhpc1XdkIcQYiz7pRagRsJ1u5lMusCAsE5gnGoEJ vplauzon@MININT-BK6A5VR" 44 | }, 45 | "resources": [ 46 | { 47 | "type": "Microsoft.Network/virtualNetworks", 48 | "apiVersion": "2017-10-01", 49 | "name": "[variables('VNet Name')]", 50 | "location": "[resourceGroup().location]", 51 | "dependsOn": [], 52 | "properties": { 53 | "addressSpace": { 54 | "addressPrefixes": [ 55 | "[variables('VNET Address Space')]" 56 | ] 57 | }, 58 | "subnets": [ 59 | { 60 | "name": "aks", 61 | "properties": { 62 | "addressPrefix": "[variables('AKS Subnet Address Space')]" 63 | } 64 | }, 65 | { 66 | "name": "services", 67 | "properties": { 68 | "addressPrefix": "[variables('Service Subnet Address Space')]" 69 | } 70 | } 71 | ] 72 | }, 73 | "resources": [] 74 | }, 75 | { 76 | "type": "Microsoft.Network/virtualNetworks/providers/roleAssignments", 77 | "apiVersion": "2017-05-01", 78 | "name": "[variables('Role Assignment Name')]", 79 | "dependsOn": [ 80 | "[resourceId('Microsoft.Network/virtualNetworks', variables('VNET Name'))]" 81 | ], 82 | "properties": { 83 | "roleDefinitionId": "[variables('Network Contributor Role')]", 84 | "principalId": "[parameters('Service Principal Object ID')]" 85 | } 86 | }, 87 | { 88 | "type": "Microsoft.ContainerService/managedClusters", 89 | "name": "cluster", 90 | "apiVersion": "2018-08-01-preview", 91 | "location": "[resourceGroup().location]", 92 | "dependsOn": [ 93 | "[resourceId('Microsoft.Network/virtualNetworks', variables('VNET Name'))]" 94 | ], 95 | "properties": { 96 | "kubernetesVersion": "[variables('kubernetes version')]", 97 | "dnsPrefix": "[parameters('DNS Prefix')]", 98 | "addonProfiles": { 99 | "httpApplicationRouting": { 100 | "enabled": true 101 | } 102 | }, 103 | "servicePrincipalProfile": { 104 | "clientId": "[parameters('Service Principal App ID')]", 105 | "secret": "[parameters('Service Principal Secret')]" 106 | }, 107 | "agentPoolProfiles": [ 108 | { 109 | "name": "agentpool", 110 | "count": "[variables('instance count')]", 111 | "vmSize": "[variables('VM Size')]", 112 | "vnetSubnetID": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('VNET Name'), 'aks')]", 113 | "maxPods": 30, 114 | "osType": "Linux", 115 | "storageProfile": "ManagedDisks", 116 | "enableAutoScaling": true, 117 | "minCount": 1, 118 | "maxCount": 5, 119 | "type": "VirtualMachineScaleSets" 120 | } 121 | ], 122 | "linuxProfile": { 123 | "adminUsername": "hidden-admin", 124 | "ssh": { 125 | "publicKeys": [ 126 | { 127 | "keyData": "[variables('ssh public key')]" 128 | } 129 | ] 130 | } 131 | }, 132 | "networkProfile": { 133 | "networkPlugin": "azure", 134 | "serviceCidr": "[variables('Service Cidr')]", 135 | "dnsServiceIP": "[variables('Dns Service IP')]", 136 | "dockerBridgeCidr": "[variables('Docker Bridge Cidr')]" 137 | } 138 | } 139 | } 140 | ], 141 | "outputs": { 142 | "controlPlaneFQDN": { 143 | "type": "string", 144 | "value": "[reference('cluster').fqdn]" 145 | } 146 | } 147 | } -------------------------------------------------------------------------------- /aks-auto-scaler/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | uniqueId=$(uuidgen) 4 | name="deploy-$uniqueId" 5 | 6 | az group deployment create -n $name -g aks --template-file deploy.json --parameters @deploy.parameters.json -------------------------------------------------------------------------------- /aks-auto-scaler/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: demo-deploy 5 | labels: 6 | app: demo-app 7 | spec: 8 | replicas: 20 9 | selector: 10 | matchLabels: 11 | app: demo-app 12 | template: 13 | metadata: 14 | labels: 15 | app: demo-app 16 | spec: 17 | containers: 18 | - name: myapi 19 | image: vplauzon/get-started:part2-no-redis 20 | resources: 21 | requests: 22 | memory: "1.5G" 23 | cpu: "250m" 24 | limits: 25 | memory: "2G" 26 | cpu: "500m" 27 | livenessProbe: 28 | httpGet: 29 | path: / 30 | port: 80 31 | initialDelaySeconds: 5 32 | ports: 33 | - containerPort: 80 -------------------------------------------------------------------------------- /aks-kubenet/README.md: -------------------------------------------------------------------------------- 1 | # AKS Kubenet vs Azure Network Integration 2 | 3 | The following button deploys an AKS cluster inside a VNET using Azure ARM Template. It offers to deploy either the *kubenet* or *Azure* Network plugins: 4 | 5 | [![Deploy button](http://azuredeploy.net/deploybutton.png)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fvplauzon%2Faks%2Fmaster%2Faks-kubenet%2Fdeploy.json) 6 | 7 | See the [following article](https://vincentlauzon.com/2018/09/06/aks-with-kubenet-vs-azure-networking-plug-in/) for details. 8 | 9 | -------------------------------------------------------------------------------- /aks-kubenet/deploy-group.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Version 3.0 2 | 3 | Param( 4 | [string] [Parameter(Mandatory=$true)] $ResourceGroupLocation, 5 | [string] $ResourceGroupName = 'test', 6 | [string] $TemplateFile = 'deploy.json', 7 | [string] $TemplateParametersFile = 'deploy.parameters.json', 8 | [switch] $ValidateOnly 9 | ) 10 | 11 | try { 12 | [Microsoft.Azure.Common.Authentication.AzureSession]::ClientFactory.AddUserAgent("VSAzureTools-$UI$($host.name)".replace(' ','_'), '3.0.0') 13 | } catch { } 14 | 15 | $ErrorActionPreference = 'Stop' 16 | Set-StrictMode -Version 3 17 | 18 | function Format-ValidationOutput { 19 | param ($ValidationOutput, [int] $Depth = 0) 20 | Set-StrictMode -Off 21 | return @($ValidationOutput | Where-Object { $_ -ne $null } | ForEach-Object { @(' ' * $Depth + ': ' + $_.Message) + @(Format-ValidationOutput @($_.Details) ($Depth + 1)) }) 22 | } 23 | 24 | $OptionalParameters = New-Object -TypeName Hashtable 25 | 26 | # Create or update the resource group using the specified template file and template parameters file 27 | New-AzureRmResourceGroup -Name $ResourceGroupName -Location $ResourceGroupLocation -Verbose -Force 28 | 29 | if ($ValidateOnly) { 30 | $ErrorMessages = Format-ValidationOutput (Test-AzureRmResourceGroupDeployment -ResourceGroupName $ResourceGroupName ` 31 | -TemplateFile $TemplateFile ` 32 | -TemplateParameterFile $TemplateParametersFile ` 33 | @OptionalParameters) 34 | if ($ErrorMessages) { 35 | Write-Output '', 'Validation returned the following errors:', @($ErrorMessages), '', 'Template is invalid.' 36 | } 37 | else { 38 | Write-Output '', 'Template is valid.' 39 | } 40 | } 41 | else { 42 | New-AzureRmResourceGroupDeployment -Name ((Get-ChildItem $TemplateFile).BaseName + '-' + ((Get-Date).ToUniversalTime()).ToString('MMdd-HHmm')) ` 43 | -ResourceGroupName $ResourceGroupName ` 44 | -TemplateFile $TemplateFile ` 45 | -TemplateParameterFile $TemplateParametersFile ` 46 | @OptionalParameters ` 47 | -Force -Verbose ` 48 | -ErrorVariable ErrorMessages 49 | if ($ErrorMessages) { 50 | Write-Output '', 'Template deployment returned the following errors:', @(@($ErrorMessages) | ForEach-Object { $_.Exception.Message.TrimEnd("`r`n") }) 51 | } 52 | } -------------------------------------------------------------------------------- /aks-kubenet/deploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "DNS Prefix": { 6 | "metadata": { 7 | "description": "DNS Prefix for the cluster" 8 | }, 9 | "type": "string" 10 | }, 11 | "Service Principal App ID": { 12 | "metadata": { 13 | "description": "App ID of the Service Principal" 14 | }, 15 | "type": "string" 16 | }, 17 | "Service Principal Object ID": { 18 | "metadata": { 19 | "description": "Object ID of the Service Principal" 20 | }, 21 | "type": "string" 22 | }, 23 | "Service Principal Secret": { 24 | "metadata": { 25 | "description": "Secret of the Service Principal" 26 | }, 27 | "type": "securestring" 28 | }, 29 | "Network Plugin": { 30 | "metadata": { 31 | "description": "Kubernetes Network Plugin" 32 | }, 33 | "allowedValues": [ 34 | "kubenet", 35 | "azure" 36 | ], 37 | "defaultValue": "kubenet", 38 | "type": "string" 39 | } 40 | }, 41 | "variables": { 42 | "isKubenet": "[equals(parameters('Network Plugin'), 'kubenet')]", 43 | "Max Pods": 200, 44 | "Network Contributor Role": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Authorization/roleDefinitions/', '4d97b98b-1d4f-4787-a291-c67834d212e7')]", 45 | "VNET Name": "cluster-vnet", 46 | "VNET Address Space": "172.16.0.0/18", 47 | "Role Assignment Name": "[concat(variables('VNET Name'), '/Microsoft.Authorization/', guid(concat(resourceGroup().id), variables('Network Contributor Role')))]", 48 | "AKS Subnet Address Space": "172.16.0.0/20", 49 | "Service Subnet Address Space": "172.16.16.0/20", 50 | "Pod Cidr": "[if(variables('isKubenet'), '10.16.0.1/16', '')]", 51 | "Cluster-IPs Service Cidr": "10.0.0.0/16", 52 | "Dns Service IP": "10.0.0.10", 53 | "Docker Bridge Cidr": "10.2.0.1/16", 54 | "kubernetes version": "1.12.6", 55 | "VM Size": "Standard_B2ms", 56 | "instance count": 3, 57 | "ssh public key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC/DWLnsCzgNo4rXoafDwXRjXBCIyX8m6sPJRVfDSmYgND739wQsfBF/B8RCyU1z+tjmOr+CZYCu6w2FmVL8JDY/aPJC9nDtO5aZSZtAdKJH51PwODsI8E4mthPuC01CxRageEDeEW9u4CCu3HXq6gFBscOEsC1iTYO5gsaxotiGdJS2pYnNHDVTWqhbzi7UPx8xPKJ1M8LKkG2paZLYBHKIhjrxjrAjnnsLkFb/dhfdr9D65Mqf5OGy40X1vQv+rfbLtnpb1DMajlfwQtBQpHY2SnEFbSwQva/l/chyhc4b854Uhpc1XdkIcQYiz7pRagRsJ1u5lMusCAsE5gnGoEJ vplauzon@MININT-BK6A5VR" 58 | }, 59 | "resources": [ 60 | { 61 | "type": "Microsoft.Network/virtualNetworks", 62 | "apiVersion": "2017-10-01", 63 | "name": "[variables('VNet Name')]", 64 | "location": "[resourceGroup().location]", 65 | "dependsOn": [], 66 | "properties": { 67 | "addressSpace": { 68 | "addressPrefixes": [ 69 | "[variables('VNET Address Space')]" 70 | ] 71 | }, 72 | "subnets": [ 73 | { 74 | "name": "aks", 75 | "properties": { 76 | "addressPrefix": "[variables('AKS Subnet Address Space')]" 77 | } 78 | }, 79 | { 80 | "name": "services", 81 | "properties": { 82 | "addressPrefix": "[variables('Service Subnet Address Space')]" 83 | } 84 | } 85 | ] 86 | }, 87 | "resources": [] 88 | }, 89 | { 90 | "type": "Microsoft.Network/virtualNetworks/providers/roleAssignments", 91 | "apiVersion": "2017-05-01", 92 | "name": "[variables('Role Assignment Name')]", 93 | "dependsOn": [ 94 | "[resourceId('Microsoft.Network/virtualNetworks', variables('VNET Name'))]" 95 | ], 96 | "properties": { 97 | "roleDefinitionId": "[variables('Network Contributor Role')]", 98 | "principalId": "[parameters('Service Principal Object ID')]" 99 | } 100 | }, 101 | { 102 | "type": "Microsoft.ContainerService/managedClusters", 103 | "name": "cluster", 104 | "apiVersion": "2018-03-31", 105 | "location": "[resourceGroup().location]", 106 | "dependsOn": [ 107 | "[resourceId('Microsoft.Network/virtualNetworks', variables('VNET Name'))]" 108 | ], 109 | "properties": { 110 | "kubernetesVersion": "[variables('kubernetes version')]", 111 | "dnsPrefix": "[parameters('DNS Prefix')]", 112 | "addonProfiles": { 113 | "httpApplicationRouting": { 114 | "enabled": true 115 | } 116 | }, 117 | "servicePrincipalProfile": { 118 | "clientId": "[parameters('Service Principal App ID')]", 119 | "secret": "[parameters('Service Principal Secret')]" 120 | }, 121 | "agentPoolProfiles": [ 122 | { 123 | "name": "agentpool", 124 | "count": "[variables('instance count')]", 125 | "vmSize": "[variables('VM Size')]", 126 | "vnetSubnetID": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('VNET Name'), 'aks')]", 127 | "maxPods": "[variables('Max Pods')]", 128 | "osType": "Linux", 129 | "storageProfile": "ManagedDisks" 130 | } 131 | ], 132 | "linuxProfile": { 133 | "adminUsername": "hidden-admin", 134 | "ssh": { 135 | "publicKeys": [ 136 | { 137 | "keyData": "[variables('ssh public key')]" 138 | } 139 | ] 140 | } 141 | }, 142 | "networkProfile": { 143 | "networkPlugin": "[parameters('Network Plugin')]", 144 | "podCidr": "[variables('Pod Cidr')]", 145 | "serviceCidr": "[variables('Cluster-IPs Service Cidr')]", 146 | "dnsServiceIP": "[variables('Dns Service IP')]", 147 | "dockerBridgeCidr": "[variables('Docker Bridge Cidr')]" 148 | } 149 | } 150 | } 151 | ], 152 | "outputs": { 153 | "controlPlaneFQDN": { 154 | "type": "string", 155 | "value": "[reference('cluster').fqdn]" 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /aks-kubenet/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: web 5 | spec: 6 | replicas: 3 7 | selector: 8 | matchLabels: 9 | app: web-get-started 10 | template: 11 | metadata: 12 | labels: 13 | app: web-get-started 14 | spec: 15 | containers: 16 | - name: myapp 17 | image: vplauzon/get-started:part2-no-redis 18 | ports: 19 | - containerPort: 80 20 | --- 21 | apiVersion: v1 22 | kind: Service 23 | metadata: 24 | name: web-service 25 | annotations: 26 | service.beta.kubernetes.io/azure-load-balancer-internal: "true" 27 | service.beta.kubernetes.io/azure-load-balancer-internal-subnet: "services" 28 | spec: 29 | type: LoadBalancer 30 | ports: 31 | - port: 80 32 | selector: 33 | app: web-get-started 34 | -------------------------------------------------------------------------------- /aks-rbac/article.md: -------------------------------------------------------------------------------- 1 | az ad app create --display-name aks-auto-server --identifier-uris http://server.aks.com --key-type Password 2 | 3 | az ad app list --display-name aks-auto-server --query "[*].appId" 4 | 5 | az ad app update --id dca27613-4bbb-418f-bb12-27d6e94fa3e2 --set groupMembershipClaims=All 6 | 7 | -------------------------------------------------------------------------------- /aks-vnet-arm/README.md: -------------------------------------------------------------------------------- 1 | # AKS Virtual Network with ARM Template 2 | 3 | The following button deploys an AKS cluster inside a VNET using Azure ARM Template: 4 | 5 | [![Deploy button](http://azuredeploy.net/deploybutton.png)](https://portal.azure.com/#create/Microsoft.Template/uri/https:%2F%2Fraw.githubusercontent.com%2Fvplauzon%2Faks%2Fmaster%2Faks-vnet-arm%2Fdeploy.json) 6 | 7 | See the [following article](https://vincentlauzon.com/2018/08/28/deploying-aks-with-arm-template-network-integration/) for details. 8 | 9 | -------------------------------------------------------------------------------- /aks-vnet-arm/deploy-group.ps1: -------------------------------------------------------------------------------- 1 | #Requires -Version 3.0 2 | 3 | Param( 4 | [string] [Parameter(Mandatory=$true)] $ResourceGroupLocation, 5 | [string] $ResourceGroupName = 'test', 6 | [string] $TemplateFile = 'deploy.json', 7 | [string] $TemplateParametersFile = 'deploy.parameters.json', 8 | [switch] $ValidateOnly 9 | ) 10 | 11 | try { 12 | [Microsoft.Azure.Common.Authentication.AzureSession]::ClientFactory.AddUserAgent("VSAzureTools-$UI$($host.name)".replace(' ','_'), '3.0.0') 13 | } catch { } 14 | 15 | $ErrorActionPreference = 'Stop' 16 | Set-StrictMode -Version 3 17 | 18 | function Format-ValidationOutput { 19 | param ($ValidationOutput, [int] $Depth = 0) 20 | Set-StrictMode -Off 21 | return @($ValidationOutput | Where-Object { $_ -ne $null } | ForEach-Object { @(' ' * $Depth + ': ' + $_.Message) + @(Format-ValidationOutput @($_.Details) ($Depth + 1)) }) 22 | } 23 | 24 | $OptionalParameters = New-Object -TypeName Hashtable 25 | 26 | # Create or update the resource group using the specified template file and template parameters file 27 | New-AzureRmResourceGroup -Name $ResourceGroupName -Location $ResourceGroupLocation -Verbose -Force 28 | 29 | if ($ValidateOnly) { 30 | $ErrorMessages = Format-ValidationOutput (Test-AzureRmResourceGroupDeployment -ResourceGroupName $ResourceGroupName ` 31 | -TemplateFile $TemplateFile ` 32 | -TemplateParameterFile $TemplateParametersFile ` 33 | @OptionalParameters) 34 | if ($ErrorMessages) { 35 | Write-Output '', 'Validation returned the following errors:', @($ErrorMessages), '', 'Template is invalid.' 36 | } 37 | else { 38 | Write-Output '', 'Template is valid.' 39 | } 40 | } 41 | else { 42 | New-AzureRmResourceGroupDeployment -Name ((Get-ChildItem $TemplateFile).BaseName + '-' + ((Get-Date).ToUniversalTime()).ToString('MMdd-HHmm')) ` 43 | -ResourceGroupName $ResourceGroupName ` 44 | -TemplateFile $TemplateFile ` 45 | -TemplateParameterFile $TemplateParametersFile ` 46 | @OptionalParameters ` 47 | -Force -Verbose ` 48 | -ErrorVariable ErrorMessages 49 | if ($ErrorMessages) { 50 | Write-Output '', 'Template deployment returned the following errors:', @(@($ErrorMessages) | ForEach-Object { $_.Exception.Message.TrimEnd("`r`n") }) 51 | } 52 | } -------------------------------------------------------------------------------- /aks-vnet-arm/deploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "DNS Prefix": { 6 | "metadata": { 7 | "description": "DNS Prefix for the cluster" 8 | }, 9 | "type": "string" 10 | }, 11 | "Service Principal App ID": { 12 | "metadata": { 13 | "description": "App ID of the Service Principal" 14 | }, 15 | "type": "string" 16 | }, 17 | "Service Principal Object ID": { 18 | "metadata": { 19 | "description": "Object ID of the Service Principal" 20 | }, 21 | "type": "string" 22 | }, 23 | "Service Principal Secret": { 24 | "metadata": { 25 | "description": "Secret of the Service Principal" 26 | }, 27 | "type": "securestring" 28 | } 29 | }, 30 | "variables": { 31 | "Network Contributor Role": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Authorization/roleDefinitions/', '4d97b98b-1d4f-4787-a291-c67834d212e7')]", 32 | "VNET Name": "cluster-vnet", 33 | "VNET Address Space": "172.16.0.0/18", 34 | "Role Assignment Name": "[concat(variables('VNET Name'), '/Microsoft.Authorization/', guid(concat(resourceGroup().id), variables('Network Contributor Role')))]", 35 | "AKS Subnet Address Space": "172.16.0.0/20", 36 | "Service Subnet Address Space": "172.16.16.0/20", 37 | "Service Cidr": "10.0.0.0/16", 38 | "Dns Service IP": "10.0.0.10", 39 | "Docker Bridge Cidr": "10.2.0.1/16", 40 | "kubernetes version": "1.11.1", 41 | "VM Size": "Standard_B2ms", 42 | "instance count": 3, 43 | "ssh public key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC/DWLnsCzgNo4rXoafDwXRjXBCIyX8m6sPJRVfDSmYgND739wQsfBF/B8RCyU1z+tjmOr+CZYCu6w2FmVL8JDY/aPJC9nDtO5aZSZtAdKJH51PwODsI8E4mthPuC01CxRageEDeEW9u4CCu3HXq6gFBscOEsC1iTYO5gsaxotiGdJS2pYnNHDVTWqhbzi7UPx8xPKJ1M8LKkG2paZLYBHKIhjrxjrAjnnsLkFb/dhfdr9D65Mqf5OGy40X1vQv+rfbLtnpb1DMajlfwQtBQpHY2SnEFbSwQva/l/chyhc4b854Uhpc1XdkIcQYiz7pRagRsJ1u5lMusCAsE5gnGoEJ vplauzon@MININT-BK6A5VR" 44 | }, 45 | "resources": [ 46 | { 47 | "type": "Microsoft.Network/virtualNetworks", 48 | "apiVersion": "2017-10-01", 49 | "name": "[variables('VNet Name')]", 50 | "location": "[resourceGroup().location]", 51 | "dependsOn": [], 52 | "properties": { 53 | "addressSpace": { 54 | "addressPrefixes": [ 55 | "[variables('VNET Address Space')]" 56 | ] 57 | }, 58 | "subnets": [ 59 | { 60 | "name": "aks", 61 | "properties": { 62 | "addressPrefix": "[variables('AKS Subnet Address Space')]" 63 | } 64 | }, 65 | { 66 | "name": "services", 67 | "properties": { 68 | "addressPrefix": "[variables('Service Subnet Address Space')]" 69 | } 70 | } 71 | ] 72 | }, 73 | "resources": [] 74 | }, 75 | { 76 | "type": "Microsoft.Network/virtualNetworks/providers/roleAssignments", 77 | "apiVersion": "2017-05-01", 78 | "name": "[variables('Role Assignment Name')]", 79 | "dependsOn": [ 80 | "[resourceId('Microsoft.Network/virtualNetworks', variables('VNET Name'))]" 81 | ], 82 | "properties": { 83 | "roleDefinitionId": "[variables('Network Contributor Role')]", 84 | "principalId": "[parameters('Service Principal Object ID')]" 85 | } 86 | }, 87 | { 88 | "type": "Microsoft.ContainerService/managedClusters", 89 | "name": "cluster", 90 | "apiVersion": "2018-03-31", 91 | "location": "[resourceGroup().location]", 92 | "dependsOn": [ 93 | "[resourceId('Microsoft.Network/virtualNetworks', variables('VNET Name'))]" 94 | ], 95 | "properties": { 96 | "kubernetesVersion": "[variables('kubernetes version')]", 97 | "dnsPrefix": "[parameters('DNS Prefix')]", 98 | "addonProfiles": { 99 | "httpApplicationRouting": { 100 | "enabled": true 101 | } 102 | }, 103 | "servicePrincipalProfile": { 104 | "clientId": "[parameters('Service Principal App ID')]", 105 | "secret": "[parameters('Service Principal Secret')]" 106 | }, 107 | "agentPoolProfiles": [ 108 | { 109 | "name": "agentpool", 110 | "count": "[variables('instance count')]", 111 | "vmSize": "[variables('VM Size')]", 112 | "vnetSubnetID": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('VNET Name'), 'aks')]", 113 | "maxPods": 30, 114 | "osType": "Linux", 115 | "storageProfile": "ManagedDisks" 116 | } 117 | ], 118 | "linuxProfile": { 119 | "adminUsername": "hidden-admin", 120 | "ssh": { 121 | "publicKeys": [ 122 | { 123 | "keyData": "[variables('ssh public key')]" 124 | } 125 | ] 126 | } 127 | }, 128 | "networkProfile": { 129 | "networkPlugin": "azure", 130 | "serviceCidr": "[variables('Service Cidr')]", 131 | "dnsServiceIP": "[variables('Dns Service IP')]", 132 | "dockerBridgeCidr": "[variables('Docker Bridge Cidr')]" 133 | } 134 | } 135 | } 136 | ], 137 | "outputs": { 138 | "controlPlaneFQDN": { 139 | "type": "string", 140 | "value": "[reference('cluster').fqdn]" 141 | } 142 | } 143 | } -------------------------------------------------------------------------------- /aks-vnet-arm/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: web 5 | spec: 6 | replicas: 3 7 | selector: 8 | matchLabels: 9 | app: web-get-started 10 | template: 11 | metadata: 12 | labels: 13 | app: web-get-started 14 | spec: 15 | containers: 16 | - name: myapp 17 | image: vplauzon/get-started:part2-no-redis 18 | ports: 19 | - containerPort: 80 20 | --- 21 | apiVersion: v1 22 | kind: Service 23 | metadata: 24 | name: web-service 25 | annotations: 26 | service.beta.kubernetes.io/azure-load-balancer-internal: "true" 27 | service.beta.kubernetes.io/azure-load-balancer-internal-subnet: "services" 28 | spec: 29 | type: LoadBalancer 30 | ports: 31 | - port: 80 32 | selector: 33 | app: web-get-started 34 | -------------------------------------------------------------------------------- /custom-logs/README.md: -------------------------------------------------------------------------------- 1 | # Custom Logs on AKS & Azure Monitor 2 | 3 | See the [following article](https://vincentlauzon.com/2019/01/30/custom-logs-on-aks-azure-monitor/) for details. 4 | -------------------------------------------------------------------------------- /custom-logs/deploy-multiple-file-to-output.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: multiple-file-to-output-deploy 5 | spec: 6 | replicas: 2 7 | selector: 8 | matchLabels: 9 | app: multiple-file-to-output-pod 10 | template: 11 | metadata: 12 | labels: 13 | app: multiple-file-to-output-pod 14 | spec: 15 | containers: 16 | - name: main-container 17 | image: busybox 18 | args: 19 | - /bin/sh 20 | - -c 21 | - > 22 | i=0; 23 | while true; 24 | do 25 | echo "$i: $(date) dog" >> /var/log/mylogs/1.log; 26 | echo "$i: $(date) cat" >> /var/log/mylogs/2.log; 27 | i=$((i+1)); 28 | sleep 1; 29 | done 30 | volumeMounts: 31 | - name: logs 32 | mountPath: /var/log/mylogs 33 | - name: log-display 34 | image: busybox 35 | args: [/bin/sh, -c, 'tail -n+1 -f /var/log/mylogs/*.log'] 36 | volumeMounts: 37 | - name: logs 38 | mountPath: /var/log/mylogs 39 | volumes: 40 | - name: logs 41 | emptyDir: {} 42 | -------------------------------------------------------------------------------- /custom-logs/multiple-file-to-host.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: multiple-file-to-host-pod 5 | spec: 6 | containers: 7 | - name: main-container 8 | image: busybox 9 | args: 10 | - /bin/sh 11 | - -c 12 | - > 13 | i=0; 14 | while true; 15 | do 16 | echo "$i: $(date) dog" >> /var/log/mylogs/1.log; 17 | echo "$i: $(date) cat" >> /var/log/mylogs/2.log; 18 | i=$((i+1)); 19 | sleep 1; 20 | done 21 | volumeMounts: 22 | - name: logs 23 | mountPath: /var/log/mylogs 24 | volumes: 25 | - name: logs 26 | hostPath: 27 | # directory location on host 28 | path: /var/log/my-app-logs 29 | type: Directory 30 | -------------------------------------------------------------------------------- /custom-logs/multiple-file-to-output.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: multiple-file-to-output-pod 5 | spec: 6 | containers: 7 | - name: main-container 8 | image: busybox 9 | args: 10 | - /bin/sh 11 | - -c 12 | - > 13 | i=0; 14 | while true; 15 | do 16 | echo "$i: $(date) dog" >> /var/log/mylogs/1.log; 17 | echo "$i: $(date) cat" >> /var/log/mylogs/2.log; 18 | i=$((i+1)); 19 | sleep 1; 20 | done 21 | volumeMounts: 22 | - name: logs 23 | mountPath: /var/log/mylogs 24 | - name: log-display 25 | image: busybox 26 | args: [/bin/sh, -c, 'tail -n+1 -f /var/log/mylogs/*.log'] 27 | volumeMounts: 28 | - name: logs 29 | mountPath: /var/log/mylogs 30 | volumes: 31 | - name: logs 32 | emptyDir: {} 33 | -------------------------------------------------------------------------------- /custom-logs/single-file-to-output.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: single-file-to-output-pod 5 | spec: 6 | containers: 7 | - name: main-container 8 | image: busybox 9 | args: 10 | - /bin/sh 11 | - -c 12 | - > 13 | i=0; 14 | while true; 15 | do 16 | echo "$i: $(date) dog" >> /var/log/mylogs/log.log; 17 | i=$((i+1)); 18 | sleep 1; 19 | done 20 | volumeMounts: 21 | - name: logs 22 | mountPath: /var/log/mylogs 23 | - name: log-display 24 | image: busybox 25 | args: [/bin/sh, -c, 'tail -n+1 -f /var/log/mylogs/log.log'] 26 | volumeMounts: 27 | - name: logs 28 | mountPath: /var/log/mylogs 29 | volumes: 30 | - name: logs 31 | emptyDir: {} 32 | -------------------------------------------------------------------------------- /custom-logs/standard-output.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: standard-output-pod 5 | spec: 6 | containers: 7 | - name: main-container 8 | image: busybox 9 | args: 10 | - /bin/sh 11 | - -c 12 | - > 13 | i=0; 14 | while true; 15 | do 16 | echo "$i: $(date) dog"; 17 | i=$((i+1)); 18 | sleep 1; 19 | done 20 | -------------------------------------------------------------------------------- /dynamic-disks/README.md: -------------------------------------------------------------------------------- 1 | # Dynamic disks in AKS 2 | 3 | See the [following article](https://vincentlauzon.com/2019/01/15/dynamic-disks-in-aks/) for details. -------------------------------------------------------------------------------- /dynamic-disks/dynamic-with-deploy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolumeClaim 3 | metadata: 4 | name: azure-managed-disk 5 | spec: 6 | accessModes: 7 | - ReadWriteOnce 8 | storageClassName: managed-premium 9 | resources: 10 | requests: 11 | storage: 25Gi 12 | --- 13 | apiVersion: apps/v1 14 | kind: Deployment 15 | metadata: 16 | name: disk-deploy 17 | labels: 18 | app: busy-box-with-disk 19 | spec: 20 | replicas: 1 21 | selector: 22 | matchLabels: 23 | app: disk-pod 24 | template: 25 | metadata: 26 | labels: 27 | app: disk-pod 28 | spec: 29 | containers: 30 | - name: busy 31 | image: vplauzon/get-started:part2-no-redis 32 | ports: 33 | - containerPort: 80 34 | volumeMounts: 35 | - name: volume 36 | mountPath: /buffer 37 | volumes: 38 | - name: volume 39 | persistentVolumeClaim: 40 | claimName: azure-managed-disk -------------------------------------------------------------------------------- /dynamic-disks/dynamic-with-stateful.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: StatefulSet 3 | metadata: 4 | name: disk-stateful-set 5 | labels: 6 | app: busy-box-with-stateful-set-disk 7 | spec: 8 | replicas: 5 9 | selector: 10 | matchLabels: 11 | app: stateful-disk-pod 12 | serviceName: stateful-disk-set 13 | template: 14 | metadata: 15 | labels: 16 | app: stateful-disk-pod 17 | spec: 18 | containers: 19 | - name: busy 20 | image: vplauzon/get-started:part2-no-redis 21 | ports: 22 | - containerPort: 80 23 | volumeMounts: 24 | - name: mydisk 25 | mountPath: /buffer 26 | volumeClaimTemplates: 27 | - metadata: 28 | name: mydisk 29 | spec: 30 | storageClassName: managed-premium 31 | accessModes: 32 | - ReadWriteOnce 33 | resources: 34 | requests: 35 | storage: 25Gi 36 | -------------------------------------------------------------------------------- /flex-volume/README.md: -------------------------------------------------------------------------------- 1 | # Flex Volume 2 | 3 | See the [following article](https://vincentlauzon.com/2019/02/21/flex-volume-in-aks/) for details. -------------------------------------------------------------------------------- /flex-volume/pod.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: access-kv 5 | labels: 6 | aadpodidbinding: little-pod-binding 7 | spec: 8 | containers: 9 | - name: main-container 10 | image: vplauzon/get-started:part2-no-redis 11 | volumeMounts: 12 | - name: secrets 13 | mountPath: /kvmnt 14 | readOnly: true 15 | volumes: 16 | - name: secrets 17 | flexVolume: 18 | driver: "azure/kv" 19 | options: 20 | usepodidentity: "true" 21 | keyvaultname: # Azure Resource name for the Azure Key Vault 22 | keyvaultobjectnames: myname;myage 23 | keyvaultobjecttypes: secret;secret # OPTIONS: secret, key, cert 24 | #keyvaultobjectversions: # If not provided, take latest 25 | resourcegroup: # Resource Group where the Key Vault is 26 | subscriptionid: # ID of the subscription where the Key Vault is 27 | tenantid: # AAD Tenant ID of the subscription -------------------------------------------------------------------------------- /http-ingress/README.md: -------------------------------------------------------------------------------- 1 | # Ingress with HTTP (no TLS) in AKS 2 | 3 | Here are some script file supporting the [following article](https://vincentlauzon.com/2018/11/28/understanding-multiple-ingress-in-aks/). 4 | 5 | -------------------------------------------------------------------------------- /http-ingress/create-cluster.sh: -------------------------------------------------------------------------------- 1 | az group create --name aks-group --location eastus2 2 | az aks create --resource-group aks-group --name aks-cluster --node-count 3 --generate-ssh-keys -s Standard_B2ms --disable-rbac 3 | az aks get-credentials -g aks-group -n aks-cluster 4 | -------------------------------------------------------------------------------- /http-ingress/domain-name-overload.yaml: -------------------------------------------------------------------------------- 1 | # Bikes deployment 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: bikes-deploy 6 | spec: 7 | replicas: 2 8 | selector: 9 | matchLabels: 10 | app: bikes 11 | template: 12 | metadata: 13 | labels: 14 | app: bikes 15 | spec: 16 | containers: 17 | - name: myapp 18 | image: vplauzon/get-started:part2-no-redis 19 | env: 20 | - name: NAME 21 | value: bikes 22 | ports: 23 | - containerPort: 80 24 | --- 25 | # Bikes Service 26 | apiVersion: v1 27 | kind: Service 28 | metadata: 29 | name: bikes-svc 30 | spec: 31 | type: ClusterIP 32 | ports: 33 | - port: 80 34 | selector: 35 | app: bikes 36 | --- 37 | # Cars deployment 38 | apiVersion: apps/v1 39 | kind: Deployment 40 | metadata: 41 | name: cars-deploy 42 | spec: 43 | replicas: 2 44 | selector: 45 | matchLabels: 46 | app: cars 47 | template: 48 | metadata: 49 | labels: 50 | app: cars 51 | spec: 52 | containers: 53 | - name: myapp 54 | image: vplauzon/get-started:part2-no-redis 55 | env: 56 | - name: NAME 57 | value: cars 58 | ports: 59 | - containerPort: 80 60 | --- 61 | # Cars Service 62 | apiVersion: v1 63 | kind: Service 64 | metadata: 65 | name: cars-svc 66 | spec: 67 | type: ClusterIP 68 | ports: 69 | - port: 80 70 | selector: 71 | app: cars 72 | --- 73 | # Domain name overload Ingress 74 | apiVersion: extensions/v1beta1 75 | kind: Ingress 76 | metadata: 77 | name: domain-name-overload-ingress 78 | annotations: 79 | kubernetes.io/ingress.class: nginx 80 | nginx.ingress.kubernetes.io/rewrite-target: / 81 | spec: 82 | rules: 83 | - host: bikes.eastus2.cloudapp.azure.com 84 | http: 85 | paths: 86 | - backend: 87 | serviceName: bikes-svc 88 | servicePort: 80 89 | - host: cars.eastus2.cloudapp.azure.com 90 | http: 91 | paths: 92 | - backend: 93 | serviceName: cars-svc 94 | servicePort: 80 95 | -------------------------------------------------------------------------------- /http-ingress/install-ingress-controller.sh: -------------------------------------------------------------------------------- 1 | helm install stable/nginx-ingress --namespace kube-system --set controller.replicaCount=2 --set rbac.create=false -------------------------------------------------------------------------------- /http-ingress/multiple-controller.yaml: -------------------------------------------------------------------------------- 1 | # Blue deployment 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: blue-deploy 6 | spec: 7 | replicas: 2 8 | selector: 9 | matchLabels: 10 | app: blue 11 | template: 12 | metadata: 13 | labels: 14 | app: blue 15 | spec: 16 | containers: 17 | - name: myapp 18 | image: vplauzon/get-started:part2-no-redis 19 | env: 20 | - name: NAME 21 | value: Blue-Pod 22 | ports: 23 | - containerPort: 80 24 | --- 25 | # Blue Service 26 | apiVersion: v1 27 | kind: Service 28 | metadata: 29 | name: blue-svc 30 | spec: 31 | type: ClusterIP 32 | ports: 33 | - port: 80 34 | selector: 35 | app: blue 36 | # White deployment 37 | apiVersion: apps/v1 38 | kind: Deployment 39 | metadata: 40 | name: white-deploy 41 | spec: 42 | replicas: 2 43 | selector: 44 | matchLabels: 45 | app: white 46 | template: 47 | metadata: 48 | labels: 49 | app: white 50 | spec: 51 | containers: 52 | - name: myapp 53 | image: vplauzon/get-started:part2-no-redis 54 | env: 55 | - name: NAME 56 | value: White-Pod 57 | ports: 58 | - containerPort: 80 59 | --- 60 | # White Service 61 | apiVersion: v1 62 | kind: Service 63 | metadata: 64 | name: white-svc 65 | spec: 66 | type: ClusterIP 67 | ports: 68 | - port: 80 69 | selector: 70 | app: white 71 | # Red deployment 72 | apiVersion: apps/v1 73 | kind: Deployment 74 | metadata: 75 | name: red-deploy 76 | spec: 77 | replicas: 2 78 | selector: 79 | matchLabels: 80 | app: red 81 | template: 82 | metadata: 83 | labels: 84 | app: red 85 | spec: 86 | containers: 87 | - name: myapp 88 | image: vplauzon/get-started:part2-no-redis 89 | env: 90 | - name: NAME 91 | value: Red-Pod 92 | ports: 93 | - containerPort: 80 94 | --- 95 | # Red Service 96 | apiVersion: v1 97 | kind: Service 98 | metadata: 99 | name: red-svc 100 | spec: 101 | type: ClusterIP 102 | ports: 103 | - port: 80 104 | selector: 105 | app: red 106 | --- 107 | # First Ingress 108 | apiVersion: extensions/v1beta1 109 | kind: Ingress 110 | metadata: 111 | name: first-ingress 112 | annotations: 113 | kubernetes.io/ingress.class: first 114 | nginx.ingress.kubernetes.io/rewrite-target: / 115 | spec: 116 | rules: 117 | - http: 118 | paths: 119 | - path: /1-blue 120 | backend: 121 | serviceName: blue-svc 122 | servicePort: 80 123 | - path: /1-white 124 | backend: 125 | serviceName: white-svc 126 | servicePort: 80 127 | - path: /1-red 128 | backend: 129 | serviceName: white-svc 130 | servicePort: 80 131 | --- 132 | # Second Ingress 133 | apiVersion: extensions/v1beta1 134 | kind: Ingress 135 | metadata: 136 | name: second-ingress 137 | annotations: 138 | kubernetes.io/ingress.class: second 139 | nginx.ingress.kubernetes.io/rewrite-target: / 140 | spec: 141 | rules: 142 | - http: 143 | paths: 144 | - path: /2-blue 145 | backend: 146 | serviceName: blue-svc 147 | servicePort: 80 148 | - path: /2-white 149 | backend: 150 | serviceName: white-svc 151 | servicePort: 80 152 | - path: /2-red 153 | backend: 154 | serviceName: white-svc 155 | servicePort: 80 156 | --- 157 | # Third Ingress 158 | apiVersion: extensions/v1beta1 159 | kind: Ingress 160 | metadata: 161 | name: third-ingress 162 | annotations: 163 | kubernetes.io/ingress.class: third 164 | nginx.ingress.kubernetes.io/rewrite-target: / 165 | spec: 166 | rules: 167 | - http: 168 | paths: 169 | - path: /3-blue 170 | backend: 171 | serviceName: blue-svc 172 | servicePort: 80 173 | - path: /3-white 174 | backend: 175 | serviceName: white-svc 176 | servicePort: 80 177 | - path: /3-red 178 | backend: 179 | serviceName: white-svc 180 | servicePort: 80 181 | -------------------------------------------------------------------------------- /http-ingress/url-based-routing.yaml: -------------------------------------------------------------------------------- 1 | # Offers deployment 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: pizza-offers-deploy 6 | spec: 7 | replicas: 2 8 | selector: 9 | matchLabels: 10 | app: pizza-offers 11 | template: 12 | metadata: 13 | labels: 14 | app: pizza-offers 15 | spec: 16 | containers: 17 | - name: myapp 18 | image: vplauzon/get-started:part2-no-redis 19 | env: 20 | - name: NAME 21 | value: pizza-offers 22 | ports: 23 | - containerPort: 80 24 | --- 25 | # Offers Service 26 | apiVersion: v1 27 | kind: Service 28 | metadata: 29 | name: pizza-offers-svc 30 | spec: 31 | type: ClusterIP 32 | ports: 33 | - port: 80 34 | selector: 35 | app: pizza-offers 36 | --- 37 | # Menu deployment 38 | apiVersion: apps/v1 39 | kind: Deployment 40 | metadata: 41 | name: pizza-menu-deploy 42 | spec: 43 | replicas: 2 44 | selector: 45 | matchLabels: 46 | app: pizza-menu 47 | template: 48 | metadata: 49 | labels: 50 | app: pizza-menu 51 | spec: 52 | containers: 53 | - name: myapp 54 | image: vplauzon/get-started:part2-no-redis 55 | env: 56 | - name: NAME 57 | value: pizza-menu 58 | ports: 59 | - containerPort: 80 60 | --- 61 | # Menu Service 62 | apiVersion: v1 63 | kind: Service 64 | metadata: 65 | name: pizza-menu-svc 66 | spec: 67 | type: ClusterIP 68 | ports: 69 | - port: 80 70 | selector: 71 | app: pizza-menu 72 | --- 73 | # Url Based Routing Ingress 74 | apiVersion: extensions/v1beta1 75 | kind: Ingress 76 | metadata: 77 | name: url-routing-ingress 78 | annotations: 79 | kubernetes.io/ingress.class: nginx 80 | nginx.ingress.kubernetes.io/rewrite-target: / 81 | spec: 82 | rules: 83 | - host: vincentpizza.eastus2.cloudapp.azure.com 84 | http: 85 | paths: 86 | - path: /pizza-offers 87 | backend: 88 | serviceName: pizza-offers-svc 89 | servicePort: 80 90 | - path: /menu 91 | backend: 92 | serviceName: pizza-menu-svc 93 | servicePort: 80 94 | -------------------------------------------------------------------------------- /ingress-multiple-ns/README.md: -------------------------------------------------------------------------------- 1 | # One Ingress Controller for multiple namespaces 2 | 3 | This demos how to deploy one Ingress Controller with Ingress rules in different namespaces. 4 | 5 | See the [following article](https://vincentlauzon.com/2020/02/11/ingress-rules-in-different-kubernetes-namespaces) for details. 6 | -------------------------------------------------------------------------------- /ingress-multiple-ns/ingress1.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Ingress 3 | metadata: 4 | name: ingress-hello-world-1 5 | namespace: hello1 6 | annotations: 7 | kubernetes.io/ingress.class: nginx 8 | nginx.ingress.kubernetes.io/ssl-redirect: "false" 9 | nginx.ingress.kubernetes.io/rewrite-target: / 10 | spec: 11 | rules: 12 | - http: 13 | paths: 14 | - backend: 15 | serviceName: aks-helloworld-one 16 | servicePort: 80 17 | path: /hello-world-1 -------------------------------------------------------------------------------- /ingress-multiple-ns/ingress2.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Ingress 3 | metadata: 4 | name: ingress-hello-world-2 5 | namespace: hello2 6 | annotations: 7 | kubernetes.io/ingress.class: nginx 8 | nginx.ingress.kubernetes.io/ssl-redirect: "false" 9 | nginx.ingress.kubernetes.io/rewrite-target: / 10 | spec: 11 | rules: 12 | - http: 13 | paths: 14 | - backend: 15 | serviceName: aks-helloworld-two 16 | servicePort: 80 17 | path: /hello-world-2 -------------------------------------------------------------------------------- /kubenet-outbound/README.md: -------------------------------------------------------------------------------- 1 | # Kubenet Outbound 2 | 3 | See the [following article](https://vincentlauzon.com/2019/03/26/testing-outbound-connections-in-aks-kubenet-and-aci/) for details. 4 | -------------------------------------------------------------------------------- /kubenet-outbound/create-cluster.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ########################################################################## 4 | ## Creates a VNET with 3 subnets 5 | ## Deploys an AKS cluster with kubenet network plugin in one subnet 6 | ## Deploys an Azure Container Instance (ACI) in another subnet 7 | ## 8 | ## Takes 5 parameters: 9 | ## 10 | ## 1- Name of resource group 11 | ## 2- Azure region name (must be compatible with ACI in VNET regions) 12 | ## 3- Name of cluster 13 | ## 4- Service Principal Application ID 14 | ## 5- Service Principal Object ID 15 | ## 6- Service Principal Password 16 | 17 | rg=$1 18 | region=$2 19 | cluster=$3 20 | appId=$4 21 | appObjectId=$5 22 | appPassword=$6 23 | 24 | echo "Resource group: $rg" 25 | echo "Region: $region" 26 | echo "Cluster name: $cluster" 27 | echo "Application ID: $appId" 28 | echo "Application Object ID: $appObjectId" 29 | echo "Application Password: $appPassword" 30 | 31 | echo 32 | echo "Creating group $rg in $region..." 33 | 34 | az group create --name $rg --location $region --query "id" -o tsv 35 | 36 | echo 37 | echo "Fetching latest version in region $region..." 38 | 39 | version=$(az aks get-versions --location $region --query "orchestrators[-1].orchestratorVersion" -o tsv) 40 | 41 | echo 42 | echo "Version: $version" 43 | 44 | echo 45 | echo "Deploying cluster $cluster, VNET, NSG & ACI..." 46 | 47 | ip=$(az group deployment create -n "deploy-$(uuidgen)" -g $rg \ 48 | --template-file deploy.json \ 49 | --parameters \ 50 | version=$version \ 51 | clusterName=$cluster \ 52 | principalAppId=$appId \ 53 | principalObjectId=$appObjectId \ 54 | principalSecret=$appPassword \ 55 | --query "properties.outputs.containerIp.value" \ 56 | -o tsv) 57 | 58 | echo 59 | echo "Successfully deployed cluster $cluster and ACI with IP $ip" 60 | echo 61 | 62 | echo "Connect kubectl to newly created cluster $cluster..." 63 | echo 64 | 65 | az aks get-credentials -g $rg -n $cluster 66 | -------------------------------------------------------------------------------- /kubenet-outbound/deploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "version": { 6 | "metadata": { 7 | "description": "Kubernetes Version" 8 | }, 9 | "type": "string", 10 | "defaultValue": "1.12.6" 11 | }, 12 | "clusterName": { 13 | "metadata": { 14 | "description": "Name of the cluster (and DNS Prefix)" 15 | }, 16 | "type": "string" 17 | }, 18 | "principalAppId": { 19 | "metadata": { 20 | "description": "App ID of the Service Principal" 21 | }, 22 | "type": "string" 23 | }, 24 | "principalObjectId": { 25 | "metadata": { 26 | "description": "Object ID of the Service Principal" 27 | }, 28 | "type": "string" 29 | }, 30 | "principalSecret": { 31 | "metadata": { 32 | "description": "Secret of the Service Principal" 33 | }, 34 | "type": "securestring" 35 | } 36 | }, 37 | "variables": { 38 | "Max Pods": 200, 39 | "Network Contributor Role": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Authorization/roleDefinitions/', '4d97b98b-1d4f-4787-a291-c67834d212e7')]", 40 | "VNET Name": "cluster-vnet", 41 | "VNET Address Space": "172.16.0.0/18", 42 | "AKS Subnet Address Space": "172.16.0.0/20", 43 | "Service Subnet Address Space": "172.16.16.0/20", 44 | "ACI Subnet Address Space": "172.16.32.0/20", 45 | "NSG": "aciNsg", 46 | "Network Profile Name": "aci-networkProfile", 47 | "Interface Config Name": "eth0", 48 | "Interface IP Config": "ipconfigprofile1", 49 | "Role Assignment Name": "[concat(variables('VNET Name'), '/Microsoft.Authorization/', guid(concat(resourceGroup().id), variables('Network Contributor Role')))]", 50 | "Container Group Name": "myContainerGroup", 51 | "Pod Cidr": "10.16.0.1/16", 52 | "Cluster-IPs Service Cidr": "10.0.0.0/16", 53 | "Dns Service IP": "10.0.0.10", 54 | "Docker Bridge Cidr": "10.2.0.1/16", 55 | "VM Size": "Standard_B2ms", 56 | "instance count": 1, 57 | "ssh public key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC/DWLnsCzgNo4rXoafDwXRjXBCIyX8m6sPJRVfDSmYgND739wQsfBF/B8RCyU1z+tjmOr+CZYCu6w2FmVL8JDY/aPJC9nDtO5aZSZtAdKJH51PwODsI8E4mthPuC01CxRageEDeEW9u4CCu3HXq6gFBscOEsC1iTYO5gsaxotiGdJS2pYnNHDVTWqhbzi7UPx8xPKJ1M8LKkG2paZLYBHKIhjrxjrAjnnsLkFb/dhfdr9D65Mqf5OGy40X1vQv+rfbLtnpb1DMajlfwQtBQpHY2SnEFbSwQva/l/chyhc4b854Uhpc1XdkIcQYiz7pRagRsJ1u5lMusCAsE5gnGoEJ vplauzon@MININT-BK6A5VR" 58 | }, 59 | "resources": [ 60 | { 61 | "type": "Microsoft.Network/networkSecurityGroups", 62 | "name": "[variables('NSG')]", 63 | "apiVersion": "2018-11-01", 64 | "location": "[resourceGroup().location]", 65 | "tags": {}, 66 | "properties": { 67 | "securityRules": [ 68 | { 69 | "name": "Allow-HTTP-From-Aks-Subnet", 70 | "properties": { 71 | "protocol": "Tcp", 72 | "sourcePortRange": "*", 73 | "destinationPortRange": "80", 74 | "sourceAddressPrefix": "[variables('AKS Subnet Address Space')]", 75 | "destinationAddressPrefix": "*", 76 | "access": "Allow", 77 | "priority": 100, 78 | "direction": "Inbound" 79 | } 80 | }, 81 | { 82 | "name": "Allow-Health-Monitoring", 83 | "properties": { 84 | "protocol": "*", 85 | "sourcePortRange": "*", 86 | "destinationPortRange": "*", 87 | "sourceAddressPrefix": "AzureLoadBalancer", 88 | "destinationAddressPrefix": "*", 89 | "access": "Allow", 90 | "priority": 200, 91 | "direction": "Inbound" 92 | } 93 | }, 94 | { 95 | "name": "Disallow-everything-else", 96 | "properties": { 97 | "protocol": "*", 98 | "sourcePortRange": "*", 99 | "destinationPortRange": "*", 100 | "sourceAddressPrefix": "*", 101 | "destinationAddressPrefix": "*", 102 | "access": "Deny", 103 | "priority": 300, 104 | "direction": "Inbound" 105 | } 106 | }, 107 | { 108 | "name": "Allow-to-VNet", 109 | "properties": { 110 | "protocol": "*", 111 | "sourcePortRange": "*", 112 | "destinationPortRange": "*", 113 | "sourceAddressPrefix": "*", 114 | "destinationAddressPrefix": "VirtualNetwork", 115 | "access": "Allow", 116 | "priority": 100, 117 | "direction": "Outbound" 118 | } 119 | }, 120 | { 121 | "name": "Deny-All-Traffic", 122 | "properties": { 123 | "protocol": "*", 124 | "sourcePortRange": "*", 125 | "destinationPortRange": "*", 126 | "sourceAddressPrefix": "*", 127 | "destinationAddressPrefix": "*", 128 | "access": "Deny", 129 | "priority": 200, 130 | "direction": "Outbound" 131 | } 132 | } 133 | ], 134 | "subnets": [] 135 | } 136 | }, 137 | { 138 | "type": "Microsoft.Network/virtualNetworks", 139 | "apiVersion": "2018-07-01", 140 | "name": "[variables('VNet Name')]", 141 | "location": "[resourceGroup().location]", 142 | "dependsOn": [ 143 | "[resourceId('Microsoft.Network/networkSecurityGroups', variables('NSG'))]" 144 | ], 145 | "properties": { 146 | "addressSpace": { 147 | "addressPrefixes": [ 148 | "[variables('VNET Address Space')]" 149 | ] 150 | }, 151 | "subnets": [ 152 | { 153 | "name": "aks", 154 | "properties": { 155 | "addressPrefix": "[variables('AKS Subnet Address Space')]" 156 | } 157 | }, 158 | { 159 | "name": "services", 160 | "properties": { 161 | "addressPrefix": "[variables('Service Subnet Address Space')]" 162 | } 163 | }, 164 | { 165 | "name": "aci", 166 | "properties": { 167 | "addressPrefix": "[variables('ACI Subnet Address Space')]", 168 | "networkSecurityGroup": { 169 | "id": "[resourceId('Microsoft.Network/networkSecurityGroups', variables('NSG'))]" 170 | }, 171 | "delegations": [ 172 | { 173 | "name": "DelegationService", 174 | "properties": { 175 | "serviceName": "Microsoft.ContainerInstance/containerGroups" 176 | } 177 | } 178 | ] 179 | } 180 | } 181 | ] 182 | }, 183 | "resources": [] 184 | }, 185 | { 186 | "name": "[variables('Network Profile Name')]", 187 | "type": "Microsoft.Network/networkProfiles", 188 | "apiVersion": "2019-04-01", 189 | "location": "[resourceGroup().location]", 190 | "dependsOn": [ 191 | "[resourceId('Microsoft.Network/virtualNetworks', variables('VNET Name'))]" 192 | ], 193 | "properties": { 194 | "containerNetworkInterfaceConfigurations": [ 195 | { 196 | "name": "[variables('Interface Config Name')]", 197 | "properties": { 198 | "ipConfigurations": [ 199 | { 200 | "name": "[variables('Interface IP Config')]", 201 | "properties": { 202 | "subnet": { 203 | "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('VNET Name'), 'aci')]" 204 | } 205 | } 206 | } 207 | ] 208 | } 209 | } 210 | ] 211 | } 212 | }, 213 | { 214 | "type": "Microsoft.Network/virtualNetworks/providers/roleAssignments", 215 | "apiVersion": "2017-05-01", 216 | "name": "[variables('Role Assignment Name')]", 217 | "dependsOn": [ 218 | "[resourceId('Microsoft.Network/virtualNetworks', variables('VNET Name'))]" 219 | ], 220 | "properties": { 221 | "roleDefinitionId": "[variables('Network Contributor Role')]", 222 | "principalId": "[parameters('principalObjectId')]" 223 | } 224 | }, 225 | { 226 | "name": "[variables('Container Group Name')]", 227 | "type": "Microsoft.ContainerInstance/containerGroups", 228 | "apiVersion": "2018-07-01", 229 | "location": "[resourceGroup().location]", 230 | "dependsOn": [ 231 | "[resourceId('Microsoft.Network/networkProfiles', variables('Network Profile Name'))]" 232 | ], 233 | "properties": { 234 | "containers": [ 235 | { 236 | "name": "web-container", 237 | "properties": { 238 | "image": "vplauzon/get-started:part2-no-redis", 239 | "ports": [ 240 | { 241 | "port": "80", 242 | "protocol": "Tcp" 243 | } 244 | ], 245 | "resources": { 246 | "requests": { 247 | "cpu": "1", 248 | "memoryInGB": "1" 249 | } 250 | } 251 | } 252 | } 253 | ], 254 | "osType": "Linux", 255 | "networkProfile": { 256 | "Id": "[resourceId('Microsoft.Network/networkProfiles', variables('Network Profile Name'))]" 257 | }, 258 | "restartPolicy": "Always" 259 | } 260 | }, 261 | { 262 | "type": "Microsoft.ContainerService/managedClusters", 263 | "name": "[parameters('clusterName')]", 264 | "apiVersion": "2018-03-31", 265 | "location": "[resourceGroup().location]", 266 | "dependsOn": [ 267 | "[resourceId('Microsoft.Network/virtualNetworks', variables('VNET Name'))]" 268 | ], 269 | "properties": { 270 | "kubernetesVersion": "[parameters('version')]", 271 | "dnsPrefix": "[parameters('clusterName')]", 272 | "addonProfiles": {}, 273 | "servicePrincipalProfile": { 274 | "clientId": "[parameters('principalAppId')]", 275 | "secret": "[parameters('principalSecret')]" 276 | }, 277 | "agentPoolProfiles": [ 278 | { 279 | "name": "agentpool", 280 | "count": "[variables('instance count')]", 281 | "vmSize": "[variables('VM Size')]", 282 | "vnetSubnetID": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('VNET Name'), 'aks')]", 283 | "maxPods": "[variables('Max Pods')]", 284 | "osType": "Linux", 285 | "storageProfile": "ManagedDisks" 286 | } 287 | ], 288 | "linuxProfile": { 289 | "adminUsername": "hidden-admin", 290 | "ssh": { 291 | "publicKeys": [ 292 | { 293 | "keyData": "[variables('ssh public key')]" 294 | } 295 | ] 296 | } 297 | }, 298 | "networkProfile": { 299 | "networkPlugin": "kubenet", 300 | "podCidr": "[variables('Pod Cidr')]", 301 | "serviceCidr": "[variables('Cluster-IPs Service Cidr')]", 302 | "dnsServiceIP": "[variables('Dns Service IP')]", 303 | "dockerBridgeCidr": "[variables('Docker Bridge Cidr')]" 304 | } 305 | } 306 | } 307 | ], 308 | "outputs": { 309 | "nodeResourceGroup": { 310 | "type": "string", 311 | "value": "[reference(parameters('clusterName')).nodeResourceGroup]" 312 | }, 313 | "containerIp": { 314 | "type": "string", 315 | "value": "[reference(resourceId('Microsoft.ContainerInstance/containerGroups/', variables('Container Group Name'))).ipAddress.ip]" 316 | } 317 | } 318 | } -------------------------------------------------------------------------------- /monitor-metrics/README.md: -------------------------------------------------------------------------------- 1 | # Monitor Metrics in AKS 2 | 3 | See the [following article](https://vincentlauzon.com/2019/05/09/monitoring-metrics-in-aks/) for details. 4 | -------------------------------------------------------------------------------- /monitor-metrics/create-cluster.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ########################################################################## 4 | ## Creates a VNET with 2 subnets 5 | ## NSG lets port 80 get into the services subnet 6 | ## Deploys an AKS cluster with kubenet network plugin in AKS subnet 7 | ## 8 | ## Takes 5 parameters: 9 | ## 10 | ## 1- Name of resource group 11 | ## 2- Azure region name (must be compatible with ACI in VNET regions) 12 | ## 3- Name of Log Analytics workspace 13 | ## 4- Name of AKS cluster 14 | ## 5- Service Principal Application ID 15 | ## 6- Service Principal Object ID 16 | ## 7- Service Principal Password 17 | 18 | # Make sure the script fails if any subcommand fail 19 | set -e 20 | 21 | rg=$1 22 | region=$2 23 | workspace=$3 24 | cluster=$4 25 | appId=$5 26 | appObjectId=$6 27 | appPassword=$7 28 | 29 | echo "Resource group: $rg" 30 | echo "Region: $region" 31 | echo "Workspace name: $workspace" 32 | echo "Cluster name: $cluster" 33 | echo "Application ID: $appId" 34 | echo "Application Object ID: $appObjectId" 35 | echo "Application Password: $appPassword" 36 | 37 | echo 38 | echo "Creating group $rg in $region..." 39 | 40 | az group create --name $rg --location $region --query "id" -o tsv 41 | 42 | echo 43 | echo "Fetching latest version in region $region..." 44 | 45 | version=$(az aks get-versions --location $region --query "orchestrators[-1].orchestratorVersion" -o tsv) 46 | 47 | echo 48 | echo "Version: $version" 49 | 50 | echo 51 | echo "Deploying cluster $cluster, Log Analytics workspace, VNET & NSG..." 52 | 53 | nrg=$(az group deployment create -n "deploy-$(uuidgen)" -g $rg \ 54 | --template-file deploy.json \ 55 | --parameters \ 56 | version=$version \ 57 | workspaceName=$workspace \ 58 | clusterName=$cluster \ 59 | principalAppId=$appId \ 60 | principalObjectId=$appObjectId \ 61 | principalSecret=$appPassword \ 62 | --query "properties.outputs.nodeResourceGroup.value" \ 63 | -o tsv) 64 | 65 | echo 66 | echo "Successfully deployed cluster $cluster" 67 | 68 | echo 69 | echo "Looking for Route table in $nrg..." 70 | 71 | routeTableId=$(az network route-table list -g $nrg --query "[0].id" -o tsv) 72 | 73 | echo 74 | echo "Looking for Virtual Network in $rg..." 75 | 76 | vnet=$(az network vnet list -g $rg --query "[0].name" -o tsv) 77 | 78 | echo 79 | echo "Connection route table $routeTableId in Virtual Network $vnet..." 80 | 81 | # Run two commands (for 2 subnets) in parallel, i.e. fork and join 82 | az network vnet subnet update -g $rg -n aks --vnet-name $vnet --route-table $routeTableId 83 | az network vnet subnet update -g $rg -n services --vnet-name $vnet --route-table $routeTableId 84 | 85 | echo 86 | echo "Connect kubectl to newly created cluster $cluster..." 87 | echo 88 | 89 | az aks get-credentials -g $rg -n $cluster -------------------------------------------------------------------------------- /monitor-metrics/deploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "version": { 6 | "metadata": { 7 | "description": "Kubernetes Version" 8 | }, 9 | "type": "string", 10 | "defaultValue": "1.12.6" 11 | }, 12 | "workspaceName": { 13 | "metadata": { 14 | "description": "Name of the Log Analytics workspace" 15 | }, 16 | "type": "string" 17 | }, 18 | "clusterName": { 19 | "metadata": { 20 | "description": "Name of the cluster (and DNS Prefix)" 21 | }, 22 | "type": "string" 23 | }, 24 | "principalAppId": { 25 | "metadata": { 26 | "description": "App ID of the Service Principal" 27 | }, 28 | "type": "string" 29 | }, 30 | "principalObjectId": { 31 | "metadata": { 32 | "description": "Object ID of the Service Principal" 33 | }, 34 | "type": "string" 35 | }, 36 | "principalSecret": { 37 | "metadata": { 38 | "description": "Secret of the Service Principal" 39 | }, 40 | "type": "securestring" 41 | } 42 | }, 43 | "variables": { 44 | "Max Pods": 32, 45 | "Network Contributor Role": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Authorization/roleDefinitions/', '4d97b98b-1d4f-4787-a291-c67834d212e7')]", 46 | "VNET Name": "cluster-vnet", 47 | "VNET Address Space": "172.16.0.0/18", 48 | "AKS Subnet Address Space": "172.16.0.0/20", 49 | "Service Subnet Address Space": "172.16.16.0/20", 50 | "NSG": "servicesNsg", 51 | "Role Assignment Name": "[concat(variables('VNET Name'), '/Microsoft.Authorization/', guid(concat(resourceGroup().id), variables('Network Contributor Role')))]", 52 | "Pod Cidr": "10.16.0.0/16", 53 | "Cluster-IPs Service Cidr": "10.0.0.0/16", 54 | "Dns Service IP": "10.0.0.10", 55 | "Docker Bridge Cidr": "10.2.0.1/16", 56 | "VM Size": "Standard_B2ms", 57 | "instance count": 1, 58 | "ssh public key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC/DWLnsCzgNo4rXoafDwXRjXBCIyX8m6sPJRVfDSmYgND739wQsfBF/B8RCyU1z+tjmOr+CZYCu6w2FmVL8JDY/aPJC9nDtO5aZSZtAdKJH51PwODsI8E4mthPuC01CxRageEDeEW9u4CCu3HXq6gFBscOEsC1iTYO5gsaxotiGdJS2pYnNHDVTWqhbzi7UPx8xPKJ1M8LKkG2paZLYBHKIhjrxjrAjnnsLkFb/dhfdr9D65Mqf5OGy40X1vQv+rfbLtnpb1DMajlfwQtBQpHY2SnEFbSwQva/l/chyhc4b854Uhpc1XdkIcQYiz7pRagRsJ1u5lMusCAsE5gnGoEJ vplauzon@MININT-BK6A5VR" 59 | }, 60 | "resources": [ 61 | { 62 | "type": "microsoft.operationalinsights/workspaces", 63 | "name": "[parameters('workspaceName')]", 64 | "apiVersion": "2015-11-01-preview", 65 | "location": "canadacentral", 66 | "properties": { 67 | "sku": { 68 | "name": "PerGB2018" 69 | } 70 | }, 71 | "dependsOn": [] 72 | }, 73 | { 74 | "type": "Microsoft.Network/networkSecurityGroups", 75 | "name": "[variables('NSG')]", 76 | "apiVersion": "2018-11-01", 77 | "location": "[resourceGroup().location]", 78 | "tags": {}, 79 | "properties": { 80 | "securityRules": [ 81 | { 82 | "name": "Allow-HTTP-From-Internet", 83 | "properties": { 84 | "protocol": "Tcp", 85 | "sourcePortRange": "*", 86 | "destinationPortRange": "80", 87 | "sourceAddressPrefix": "*", 88 | "destinationAddressPrefix": "[variables('Service Subnet Address Space')]", 89 | "access": "Allow", 90 | "priority": 100, 91 | "direction": "Inbound" 92 | } 93 | } 94 | ], 95 | "subnets": [] 96 | } 97 | }, 98 | { 99 | "type": "Microsoft.Network/virtualNetworks", 100 | "apiVersion": "2018-07-01", 101 | "name": "[variables('VNet Name')]", 102 | "location": "[resourceGroup().location]", 103 | "dependsOn": [ 104 | "[resourceId('Microsoft.Network/networkSecurityGroups', variables('NSG'))]" 105 | ], 106 | "properties": { 107 | "addressSpace": { 108 | "addressPrefixes": [ 109 | "[variables('VNET Address Space')]" 110 | ] 111 | }, 112 | "subnets": [ 113 | { 114 | "name": "aks", 115 | "properties": { 116 | "addressPrefix": "[variables('AKS Subnet Address Space')]" 117 | } 118 | }, 119 | { 120 | "name": "services", 121 | "properties": { 122 | "addressPrefix": "[variables('Service Subnet Address Space')]", 123 | "networkSecurityGroup": { 124 | "id": "[resourceId('Microsoft.Network/networkSecurityGroups', variables('NSG'))]" 125 | } 126 | } 127 | } 128 | ] 129 | }, 130 | "resources": [] 131 | }, 132 | { 133 | "type": "Microsoft.Network/virtualNetworks/providers/roleAssignments", 134 | "apiVersion": "2017-05-01", 135 | "name": "[variables('Role Assignment Name')]", 136 | "dependsOn": [ 137 | "[resourceId('Microsoft.Network/virtualNetworks', variables('VNET Name'))]" 138 | ], 139 | "properties": { 140 | "roleDefinitionId": "[variables('Network Contributor Role')]", 141 | "principalId": "[parameters('principalObjectId')]" 142 | } 143 | }, 144 | { 145 | "type": "Microsoft.ContainerService/managedClusters", 146 | "name": "[parameters('clusterName')]", 147 | "apiVersion": "2019-02-01", 148 | "location": "[resourceGroup().location]", 149 | "dependsOn": [ 150 | "[resourceId('Microsoft.Network/virtualNetworks', variables('VNET Name'))]", 151 | "[resourceId('microsoft.operationalinsights/workspaces', parameters('workspaceName'))]" 152 | ], 153 | "properties": { 154 | "kubernetesVersion": "[parameters('version')]", 155 | "dnsPrefix": "[parameters('clusterName')]", 156 | "addonProfiles": { 157 | "omsagent": { 158 | "enabled": true, 159 | "config": { 160 | "logAnalyticsWorkspaceResourceID": "[resourceId('microsoft.operationalinsights/workspaces', parameters('workspaceName'))]" 161 | } 162 | } 163 | }, 164 | "servicePrincipalProfile": { 165 | "clientId": "[parameters('principalAppId')]", 166 | "secret": "[parameters('principalSecret')]" 167 | }, 168 | "agentPoolProfiles": [ 169 | { 170 | "name": "agentpool", 171 | "count": "[variables('instance count')]", 172 | "vmSize": "[variables('VM Size')]", 173 | "vnetSubnetID": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('VNET Name'), 'aks')]", 174 | "maxPods": "[variables('Max Pods')]", 175 | "osType": "Linux", 176 | "storageProfile": "ManagedDisks" 177 | } 178 | ], 179 | "linuxProfile": { 180 | "adminUsername": "hidden-admin", 181 | "ssh": { 182 | "publicKeys": [ 183 | { 184 | "keyData": "[variables('ssh public key')]" 185 | } 186 | ] 187 | } 188 | }, 189 | "networkProfile": { 190 | "networkPlugin": "kubenet", 191 | "podCidr": "[variables('Pod Cidr')]", 192 | "serviceCidr": "[variables('Cluster-IPs Service Cidr')]", 193 | "dnsServiceIP": "[variables('Dns Service IP')]", 194 | "dockerBridgeCidr": "[variables('Docker Bridge Cidr')]" 195 | } 196 | } 197 | } 198 | ], 199 | "outputs": { 200 | "nodeResourceGroup": { 201 | "type": "string", 202 | "value": "[reference(parameters('clusterName')).nodeResourceGroup]" 203 | } 204 | } 205 | } -------------------------------------------------------------------------------- /monitor-metrics/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: cpu-ram-api 5 | spec: 6 | replicas: 6 7 | selector: 8 | matchLabels: 9 | app: cpu-ram-api 10 | template: 11 | metadata: 12 | labels: 13 | app: cpu-ram-api 14 | spec: 15 | containers: 16 | - name: cpu-ram-request-api 17 | image: vplauzon/cpu-ram-request-api:4 18 | ports: 19 | - containerPort: 80 20 | resources: 21 | requests: 22 | memory: "64M" 23 | cpu: "250m" 24 | limits: 25 | memory: "128M" 26 | cpu: "2" 27 | --- 28 | apiVersion: v1 29 | kind: Service 30 | metadata: 31 | name: cpu-ram-request-api-svc 32 | spec: 33 | type: LoadBalancer 34 | ports: 35 | - port: 80 36 | selector: 37 | app: cpu-ram-api 38 | -------------------------------------------------------------------------------- /requests-vs-limits/CpuRamRequestSolution/CpuRamRequestApi/Controllers/ValuesController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Microsoft.AspNetCore.Mvc; 8 | 9 | namespace CpuRamRequestApi.Controllers 10 | { 11 | [Route("/")] 12 | [ApiController] 13 | public class ValuesController : ControllerBase 14 | { 15 | // GET / 16 | [HttpGet] 17 | public async Task> GetAsync( 18 | [FromQuery]int duration = 1, 19 | [FromQuery]int core = 1, 20 | [FromQuery]int ram = 10) 21 | { 22 | try 23 | { 24 | core = Math.Max(core, 1); 25 | core = Math.Min(core, 256); 26 | ram = Math.Max(1, ram); 27 | ram = Math.Min(10000, ram); 28 | duration = Math.Max(duration, 1); 29 | duration = Math.Min(duration, 300); 30 | 31 | var source = new CancellationTokenSource(TimeSpan.FromSeconds(duration)); 32 | var cancellationToken = source.Token; 33 | var stopWatch = Stopwatch.StartNew(); 34 | var tasks = (from i in Enumerable.Range(0, core) 35 | let task = Task.Run(() => BusyWork(ram, cancellationToken), cancellationToken) 36 | select task).ToArray(); 37 | 38 | await Task.WhenAll(tasks); 39 | 40 | return new 41 | { 42 | duration = duration, 43 | numberOfCore = core, 44 | ram = ram, 45 | realDuration = stopWatch.Elapsed 46 | }; 47 | } 48 | catch (AggregateException ex) 49 | { 50 | var messages = from e in ex.InnerExceptions 51 | select e.Message; 52 | 53 | return new 54 | { 55 | exceptions = messages.ToArray() 56 | }; 57 | } 58 | catch (Exception ex) 59 | { 60 | return new 61 | { 62 | exception = ex.Message 63 | }; 64 | } 65 | } 66 | 67 | private void BusyWork(int ram, CancellationToken cancellationToken) 68 | { 69 | var ramChunk = new byte[ram * 1024 * 1024]; 70 | var random = new Random(); 71 | int i = 0; 72 | 73 | while (!cancellationToken.IsCancellationRequested) 74 | { 75 | ramChunk[i] = (byte)random.Next(0, 255); 76 | ++i; 77 | if (i >= ramChunk.Length) 78 | { 79 | i = 0; 80 | } 81 | } 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /requests-vs-limits/CpuRamRequestSolution/CpuRamRequestApi/CpuRamRequestApi.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | true 9 | 10 | 11 | 12 | 13 | true 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /requests-vs-limits/CpuRamRequestSolution/CpuRamRequestApi/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace CpuRamRequestApi 12 | { 13 | public class Program 14 | { 15 | public static void Main(string[] args) 16 | { 17 | CreateWebHostBuilder(args).Build().Run(); 18 | } 19 | 20 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 21 | WebHost.CreateDefaultBuilder(args) 22 | .UseStartup(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /requests-vs-limits/CpuRamRequestSolution/CpuRamRequestApi/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.AspNetCore.Mvc; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.DependencyInjection; 10 | using Microsoft.Extensions.Logging; 11 | using Microsoft.Extensions.Options; 12 | 13 | namespace CpuRamRequestApi 14 | { 15 | public class Startup 16 | { 17 | public Startup(IConfiguration configuration) 18 | { 19 | Configuration = configuration; 20 | } 21 | 22 | public IConfiguration Configuration { get; } 23 | 24 | // This method gets called by the runtime. Use this method to add services to the container. 25 | public void ConfigureServices(IServiceCollection services) 26 | { 27 | services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); 28 | } 29 | 30 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 31 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 32 | { 33 | if (env.IsDevelopment()) 34 | { 35 | app.UseDeveloperExceptionPage(); 36 | } 37 | 38 | app.UseMvc(); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /requests-vs-limits/CpuRamRequestSolution/CpuRamRequestApi/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /requests-vs-limits/CpuRamRequestSolution/CpuRamRequestApi/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "AllowedHosts": "*" 8 | } 9 | -------------------------------------------------------------------------------- /requests-vs-limits/CpuRamRequestSolution/CpuRamRequestSolution.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.28714.193 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B674C287-36DB-4605-AC30-13D877D98309}" 7 | ProjectSection(SolutionItems) = preProject 8 | build-container.sh = build-container.sh 9 | Dockerfile = Dockerfile 10 | EndProjectSection 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CpuRamRequestApi", "CpuRamRequestApi\CpuRamRequestApi.csproj", "{AB903585-92B5-4179-8229-10E9DB1CB8EF}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {AB903585-92B5-4179-8229-10E9DB1CB8EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {AB903585-92B5-4179-8229-10E9DB1CB8EF}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {AB903585-92B5-4179-8229-10E9DB1CB8EF}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {AB903585-92B5-4179-8229-10E9DB1CB8EF}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {1B28BDFF-61DB-4BF3-B5CB-602F60CE1211} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /requests-vs-limits/CpuRamRequestSolution/Dockerfile: -------------------------------------------------------------------------------- 1 | # Multi-stage docker build file (see https://docs.docker.com/develop/develop-images/multistage-build/) 2 | # Use a Microsoft image with .NET core runtime (https://hub.docker.com/_/microsoft-dotnet-core-sdk/) 3 | FROM mcr.microsoft.com/dotnet/core/sdk:2.2 AS build 4 | 5 | WORKDIR /src 6 | 7 | # Copy source code into the source folder 8 | COPY . . 9 | 10 | # Publish the app into the app folder 11 | RUN dotnet publish CpuRamRequestApi -c release -o app 12 | 13 | ########################################################### 14 | # Final container image 15 | # Use a Microsoft image with .NET core runtime (https://hub.docker.com/_/microsoft-dotnet-core-aspnet) 16 | FROM mcr.microsoft.com/dotnet/core/aspnet:2.2 AS final 17 | 18 | # Set the working directory to /work 19 | WORKDIR /work 20 | 21 | # Copy package 22 | COPY --from=build /src/CpuRamRequestApi/app . 23 | 24 | # Make port 80 available to the world outside this container 25 | 26 | EXPOSE 80 27 | 28 | # Define environment variables 29 | ENV TODO "" 30 | 31 | # Run console app 32 | CMD ["dotnet", "CpuRamRequestApi.dll"] -------------------------------------------------------------------------------- /requests-vs-limits/CpuRamRequestSolution/build-container.sh: -------------------------------------------------------------------------------- 1 | # This script isn't used in CI / CD ; it is there for manual testing if needed 2 | 3 | # Build docker container 4 | sudo docker build -t vplauzon/cpu-ram-request-api:4 . 5 | 6 | # Publish image 7 | sudo docker push vplauzon/cpu-ram-request-api:4 8 | 9 | # Test image 10 | sudo docker run --name test-api -d -p 4000:80 vplauzon/cpu-ram-request-api:4 11 | curl localhost:4000 12 | # Get into image 13 | sudo docker run --name test-api -it vplauzon/cpu-ram-request-api:4 -p 4000:80 bash 14 | # Clean up after test 15 | sudo docker stop test-api && sudo docker container prune -f -------------------------------------------------------------------------------- /requests-vs-limits/README.md: -------------------------------------------------------------------------------- 1 | # Request vs Limits 2 | 3 | See the [following article](https://vincentlauzon.com/2019/04/02/requests-vs-limits-in-kubernetes/) for details. 4 | -------------------------------------------------------------------------------- /requests-vs-limits/create-cluster.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ########################################################################## 4 | ## Creates a VNET with 2 subnets 5 | ## NSG lets port 80 get into the services subnet 6 | ## Deploys an AKS cluster with kubenet network plugin in AKS subnet 7 | ## 8 | ## Takes 5 parameters: 9 | ## 10 | ## 1- Name of resource group 11 | ## 2- Azure region name (must be compatible with ACI in VNET regions) 12 | ## 3- Name of Log Analytics workspace 13 | ## 4- Name of AKS cluster 14 | ## 5- Service Principal Application ID 15 | ## 6- Service Principal Object ID 16 | ## 7- Service Principal Password 17 | 18 | # Make sure the script fails if any subcommand fail 19 | set -e 20 | 21 | rg=$1 22 | region=$2 23 | workspace=$3 24 | cluster=$4 25 | appId=$5 26 | appObjectId=$6 27 | appPassword=$7 28 | 29 | echo "Resource group: $rg" 30 | echo "Region: $region" 31 | echo "Workspace name: $workspace" 32 | echo "Cluster name: $cluster" 33 | echo "Application ID: $appId" 34 | echo "Application Object ID: $appObjectId" 35 | echo "Application Password: $appPassword" 36 | 37 | echo 38 | echo "Creating group $rg in $region..." 39 | 40 | az group create --name $rg --location $region --query "id" -o tsv 41 | 42 | echo 43 | echo "Fetching latest version in region $region..." 44 | 45 | version=$(az aks get-versions --location $region --query "orchestrators[-1].orchestratorVersion" -o tsv) 46 | 47 | echo 48 | echo "Version: $version" 49 | 50 | echo 51 | echo "Deploying cluster $cluster, Log Analytics workspace, VNET & NSG..." 52 | 53 | nrg=$(az group deployment create -n "deploy-$(uuidgen)" -g $rg \ 54 | --template-file deploy.json \ 55 | --parameters \ 56 | version=$version \ 57 | workspaceName=$workspace \ 58 | clusterName=$cluster \ 59 | principalAppId=$appId \ 60 | principalObjectId=$appObjectId \ 61 | principalSecret=$appPassword \ 62 | --query "properties.outputs.nodeResourceGroup.value" \ 63 | -o tsv) 64 | 65 | echo 66 | echo "Successfully deployed cluster $cluster" 67 | 68 | echo 69 | echo "Looking for Route table in $nrg..." 70 | 71 | routeTableId=$(az network route-table list -g $nrg --query "[0].id" -o tsv) 72 | 73 | echo 74 | echo "Looking for Virtual Network in $rg..." 75 | 76 | vnet=$(az network vnet list -g $rg --query "[0].name" -o tsv) 77 | 78 | echo 79 | echo "Connection route table $routeTableId in Virtual Network $vnet..." 80 | 81 | # Run two commands (for 2 subnets) in parallel, i.e. fork and join 82 | az network vnet subnet update -g $rg -n aks --vnet-name $vnet --route-table $routeTableId 83 | az network vnet subnet update -g $rg -n services --vnet-name $vnet --route-table $routeTableId 84 | 85 | echo 86 | echo "Connect kubectl to newly created cluster $cluster..." 87 | echo 88 | 89 | az aks get-credentials -g $rg -n $cluster -------------------------------------------------------------------------------- /requests-vs-limits/deploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "version": { 6 | "metadata": { 7 | "description": "Kubernetes Version" 8 | }, 9 | "type": "string", 10 | "defaultValue": "1.12.6" 11 | }, 12 | "workspaceName": { 13 | "metadata": { 14 | "description": "Name of the Log Analytics workspace" 15 | }, 16 | "type": "string" 17 | }, 18 | "clusterName": { 19 | "metadata": { 20 | "description": "Name of the cluster (and DNS Prefix)" 21 | }, 22 | "type": "string" 23 | }, 24 | "principalAppId": { 25 | "metadata": { 26 | "description": "App ID of the Service Principal" 27 | }, 28 | "type": "string" 29 | }, 30 | "principalObjectId": { 31 | "metadata": { 32 | "description": "Object ID of the Service Principal" 33 | }, 34 | "type": "string" 35 | }, 36 | "principalSecret": { 37 | "metadata": { 38 | "description": "Secret of the Service Principal" 39 | }, 40 | "type": "securestring" 41 | } 42 | }, 43 | "variables": { 44 | "Max Pods": 32, 45 | "Network Contributor Role": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Authorization/roleDefinitions/', '4d97b98b-1d4f-4787-a291-c67834d212e7')]", 46 | "VNET Name": "cluster-vnet", 47 | "VNET Address Space": "172.16.0.0/18", 48 | "AKS Subnet Address Space": "172.16.0.0/20", 49 | "Service Subnet Address Space": "172.16.16.0/20", 50 | "NSG": "servicesNsg", 51 | "Role Assignment Name": "[concat(variables('VNET Name'), '/Microsoft.Authorization/', guid(concat(resourceGroup().id), variables('Network Contributor Role')))]", 52 | "Pod Cidr": "10.16.0.0/16", 53 | "Cluster-IPs Service Cidr": "10.0.0.0/16", 54 | "Dns Service IP": "10.0.0.10", 55 | "Docker Bridge Cidr": "10.2.0.1/16", 56 | "VM Size": "Standard_B2ms", 57 | "instance count": 1, 58 | "ssh public key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC/DWLnsCzgNo4rXoafDwXRjXBCIyX8m6sPJRVfDSmYgND739wQsfBF/B8RCyU1z+tjmOr+CZYCu6w2FmVL8JDY/aPJC9nDtO5aZSZtAdKJH51PwODsI8E4mthPuC01CxRageEDeEW9u4CCu3HXq6gFBscOEsC1iTYO5gsaxotiGdJS2pYnNHDVTWqhbzi7UPx8xPKJ1M8LKkG2paZLYBHKIhjrxjrAjnnsLkFb/dhfdr9D65Mqf5OGy40X1vQv+rfbLtnpb1DMajlfwQtBQpHY2SnEFbSwQva/l/chyhc4b854Uhpc1XdkIcQYiz7pRagRsJ1u5lMusCAsE5gnGoEJ vplauzon@MININT-BK6A5VR" 59 | }, 60 | "resources": [ 61 | { 62 | "type": "microsoft.operationalinsights/workspaces", 63 | "name": "[parameters('workspaceName')]", 64 | "apiVersion": "2015-11-01-preview", 65 | "location": "canadacentral", 66 | "properties": { 67 | "sku": { 68 | "name": "pergb2018" 69 | } 70 | }, 71 | "dependsOn": [] 72 | }, 73 | { 74 | "type": "Microsoft.Network/networkSecurityGroups", 75 | "name": "[variables('NSG')]", 76 | "apiVersion": "2018-11-01", 77 | "location": "[resourceGroup().location]", 78 | "tags": {}, 79 | "properties": { 80 | "securityRules": [ 81 | { 82 | "name": "Allow-HTTP-From-Internet", 83 | "properties": { 84 | "protocol": "Tcp", 85 | "sourcePortRange": "*", 86 | "destinationPortRange": "80", 87 | "sourceAddressPrefix": "*", 88 | "destinationAddressPrefix": "[variables('Service Subnet Address Space')]", 89 | "access": "Allow", 90 | "priority": 100, 91 | "direction": "Inbound" 92 | } 93 | } 94 | ], 95 | "subnets": [] 96 | } 97 | }, 98 | { 99 | "type": "Microsoft.Network/virtualNetworks", 100 | "apiVersion": "2018-07-01", 101 | "name": "[variables('VNet Name')]", 102 | "location": "[resourceGroup().location]", 103 | "dependsOn": [ 104 | "[resourceId('Microsoft.Network/networkSecurityGroups', variables('NSG'))]" 105 | ], 106 | "properties": { 107 | "addressSpace": { 108 | "addressPrefixes": [ 109 | "[variables('VNET Address Space')]" 110 | ] 111 | }, 112 | "subnets": [ 113 | { 114 | "name": "aks", 115 | "properties": { 116 | "addressPrefix": "[variables('AKS Subnet Address Space')]" 117 | } 118 | }, 119 | { 120 | "name": "services", 121 | "properties": { 122 | "addressPrefix": "[variables('Service Subnet Address Space')]", 123 | "networkSecurityGroup": { 124 | "id": "[resourceId('Microsoft.Network/networkSecurityGroups', variables('NSG'))]" 125 | } 126 | } 127 | } 128 | ] 129 | }, 130 | "resources": [] 131 | }, 132 | { 133 | "type": "Microsoft.Network/virtualNetworks/providers/roleAssignments", 134 | "apiVersion": "2017-05-01", 135 | "name": "[variables('Role Assignment Name')]", 136 | "dependsOn": [ 137 | "[resourceId('Microsoft.Network/virtualNetworks', variables('VNET Name'))]" 138 | ], 139 | "properties": { 140 | "roleDefinitionId": "[variables('Network Contributor Role')]", 141 | "principalId": "[parameters('principalObjectId')]" 142 | } 143 | }, 144 | { 145 | "type": "Microsoft.ContainerService/managedClusters", 146 | "name": "[parameters('clusterName')]", 147 | "apiVersion": "2019-02-01", 148 | "location": "[resourceGroup().location]", 149 | "dependsOn": [ 150 | "[resourceId('Microsoft.Network/virtualNetworks', variables('VNET Name'))]", 151 | "[resourceId('microsoft.operationalinsights/workspaces', parameters('workspaceName'))]" 152 | ], 153 | "properties": { 154 | "kubernetesVersion": "[parameters('version')]", 155 | "dnsPrefix": "[parameters('clusterName')]", 156 | "addonProfiles": { 157 | "omsagent": { 158 | "enabled": true, 159 | "config": { 160 | "logAnalyticsWorkspaceResourceID": "[resourceId('microsoft.operationalinsights/workspaces', parameters('workspaceName'))]" 161 | } 162 | } 163 | }, 164 | "servicePrincipalProfile": { 165 | "clientId": "[parameters('principalAppId')]", 166 | "secret": "[parameters('principalSecret')]" 167 | }, 168 | "agentPoolProfiles": [ 169 | { 170 | "name": "agentpool", 171 | "count": "[variables('instance count')]", 172 | "vmSize": "[variables('VM Size')]", 173 | "vnetSubnetID": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('VNET Name'), 'aks')]", 174 | "maxPods": "[variables('Max Pods')]", 175 | "osType": "Linux", 176 | "storageProfile": "ManagedDisks" 177 | } 178 | ], 179 | "linuxProfile": { 180 | "adminUsername": "hidden-admin", 181 | "ssh": { 182 | "publicKeys": [ 183 | { 184 | "keyData": "[variables('ssh public key')]" 185 | } 186 | ] 187 | } 188 | }, 189 | "networkProfile": { 190 | "networkPlugin": "kubenet", 191 | "podCidr": "[variables('Pod Cidr')]", 192 | "serviceCidr": "[variables('Cluster-IPs Service Cidr')]", 193 | "dnsServiceIP": "[variables('Dns Service IP')]", 194 | "dockerBridgeCidr": "[variables('Docker Bridge Cidr')]" 195 | } 196 | } 197 | } 198 | ], 199 | "outputs": { 200 | "nodeResourceGroup": { 201 | "type": "string", 202 | "value": "[reference(parameters('clusterName')).nodeResourceGroup]" 203 | } 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /requests-vs-limits/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: cpu-ram-api 5 | spec: 6 | replicas: 6 7 | selector: 8 | matchLabels: 9 | app: cpu-ram-api 10 | template: 11 | metadata: 12 | labels: 13 | app: cpu-ram-api 14 | spec: 15 | containers: 16 | - name: myapp 17 | image: vplauzon/cpu-ram-request-api:4 18 | ports: 19 | - containerPort: 80 20 | resources: 21 | requests: 22 | memory: "64M" 23 | cpu: "250m" 24 | limits: 25 | memory: "128M" 26 | cpu: "2" 27 | --- 28 | apiVersion: v1 29 | kind: Service 30 | metadata: 31 | name: web-service 32 | spec: 33 | type: LoadBalancer 34 | ports: 35 | - port: 80 36 | selector: 37 | app: cpu-ram-api 38 | --------------------------------------------------------------------------------