diff --git a/README.md b/README.md index 1ba3d003f..d8c1caccf 100644 --- a/README.md +++ b/README.md @@ -176,6 +176,7 @@ Example | Description | --------- | --------- | [Azure Container Instance](azure-py-aci) | Run Azure Container Instances on Linux. [Azure Kubernetes Service](azure-py-aks) | Create an Azure Kubernetes Service (AKS) Cluster. +[Azure AKS with Container Registry](azure-py-aks-acr-keda) | Create a AKS Cluster with an Azure Container Registry and deploy Keda with Helm. [Azure App Service](azure-py-appservice) | Build a web application hosted in App Service and provision Azure SQL Database and Azure Application Insights. [Azure App Service with Docker](azure-py-appservice-docker) | Build a web application hosted in App Service from Docker images. [Azure SDK integration](azure-py-call-azure-sdk) | Call Azure SDK functions from a Pulumi program in Python. diff --git a/azure-py-aks-acr-keda/.gitignore b/azure-py-aks-acr-keda/.gitignore new file mode 100644 index 000000000..51c9a3e57 --- /dev/null +++ b/azure-py-aks-acr-keda/.gitignore @@ -0,0 +1,5 @@ +*.pyc +venv/ +Pulumi.aks.yaml +.idea +Pulumi.dev.yaml \ No newline at end of file diff --git a/azure-py-aks-acr-keda/LICENSE b/azure-py-aks-acr-keda/LICENSE new file mode 100644 index 000000000..160dfad03 --- /dev/null +++ b/azure-py-aks-acr-keda/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Cassio Rogerio Eskelsen + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/azure-py-aks-acr-keda/Pulumi.yaml b/azure-py-aks-acr-keda/Pulumi.yaml new file mode 100644 index 000000000..4af9cad61 --- /dev/null +++ b/azure-py-aks-acr-keda/Pulumi.yaml @@ -0,0 +1,6 @@ +name: kubernetes-acr-keda +runtime: + name: python + options: + virtualenv: venv +description: k8s Cluster with Keda and Azure Contaier Register diff --git a/azure-py-aks-acr-keda/README.md b/azure-py-aks-acr-keda/README.md new file mode 100644 index 000000000..88c1a150f --- /dev/null +++ b/azure-py-aks-acr-keda/README.md @@ -0,0 +1,70 @@ +[![Deploy](https://get.pulumi.com/new/button.svg)](https://app.pulumi.com/new) + +# Azure Kubernetes Service (AKS) Cluster with Azure Container Registry and Keda + +This example deploys an AKS cluster, creates an Azure Active AD application, creates a Service Principal, sets credentials to manage access to the cluster create, associate a Azure Registry Container and deploy Keda with Helm. + +## Deploying the App + +To deploy your infrastructure, follow the below steps. + +### Prerequisites + +1. [Install Pulumi](https://www.pulumi.com/docs/get-started/install/) +2. [Install Python 3.6 or higher](https://www.python.org/downloads/) +3. [Configure Azure Credentials](https://www.pulumi.com/docs/intro/cloud-providers/azure/setup/) + +Important: if you have more than one Azure Subscription, make sure that de deploy will be done in the correct one with this command: + + ```bash + $ az account set --subscription subscription_id + ``` + +### Steps + +After cloning this repo, from this working directory, run these commands: + +1. Create a new stack, which is an isolated deployment target for this example: + + ```bash + $ pulumi stack init + ``` + +1. Set the Azure region location to use: + + ``` + $ pulumi config set azure-native:location eastus + ``` + +1. Optionaly set a prefix for all names (like your product or squad name) + + ``` + $ pulumi config set name_prefix my-fancy-project + ``` + +1. Initiate pulumi to stand up the cluster + + ```bash + $ pulumi up + ``` + +1. After 3-4 minutes, your cluster will be ready, and the kubeconfig YAML you'll use to connect to the cluster will be available as an output. You can save this kubeconfig to a file like so: + + ```bash + $ pulumi stack output kubeconfig --show-secrets > kubeconfig.yaml + ``` + + Once you have this file in hand, you can interact with your new cluster as usual via `kubectl`: + + ```bash + $ KUBECONFIG=./kubeconfig.yaml kubectl get nodes + ``` + +1. From there, feel free to experiment. Simply making edits and running `pulumi up` will incrementally update your stack. + +1. Once you've finished experimenting, tear down your stack's resources by destroying and removing it: + + ```bash + $ pulumi destroy --yes + $ pulumi stack rm --yes + ``` \ No newline at end of file diff --git a/azure-py-aks-acr-keda/__main__.py b/azure-py-aks-acr-keda/__main__.py new file mode 100644 index 000000000..155f72f64 --- /dev/null +++ b/azure-py-aks-acr-keda/__main__.py @@ -0,0 +1,21 @@ +import pulumi +from pulumi.resource import ResourceOptions +from pulumi_kubernetes.helm.v3 import Chart, ChartOpts + +import cluster + +def remove_status(obj, opts): + if obj["kind"] == "CustomResourceDefinition": + del obj["status"] + +apache = Chart('keda-chart', + ChartOpts( + chart='keda', + version='2.3.0', + transformations=[remove_status], + fetch_opts={'repo': 'https://kedacore.github.io/charts'}), + ResourceOptions(provider=cluster.k8s_provider)) + + +pulumi.export('cluster_name', cluster.k8s_cluster.name) +pulumi.export('kubeconfig', cluster.kubeconfig) diff --git a/azure-py-aks-acr-keda/cluster.py b/azure-py-aks-acr-keda/cluster.py new file mode 100644 index 000000000..72f08c4ab --- /dev/null +++ b/azure-py-aks-acr-keda/cluster.py @@ -0,0 +1,92 @@ +import base64 + +import pulumi +import pulumi_azure as azure +import pulumi_azuread as azuread +import pulumi_kubernetes as k8s +from pulumi_azure_native import resources, containerservice, containerregistry, authorization, web + +import config + +resource_group = resources.ResourceGroup(f'{config.resource_name_prefix}-rg-aks', tags=config.default_tags) + +ad_app = azuread.Application('app', display_name='app') + +ad_sp = azuread.ServicePrincipal(f'{config.resource_name_prefix}-service-principal', + application_id=ad_app.application_id) + +ad_sp_password = azuread.ServicePrincipalPassword('sp-password', + service_principal_id=ad_sp.id, + value=config.password, + end_date='2099-01-01T00:00:00Z') + +acr = containerregistry.Registry(f'{config.acr_name}acr', + resource_group_name=resource_group.name, + registry_name=f'{config.acr_name}acr', + sku=web.SkuDescriptionArgs( + name="Basic", + ), + tags=config.default_tags, + admin_user_enabled=True + ) + +primary = azure.core.get_subscription() + +role_definition = authorization.RoleDefinition(f'{config.resource_name_prefix}-roleDefinition', + role_name="f{config.resource_name_prefix}-roleDefinition", + scope=primary.id, + permissions=[azure.authorization.RoleDefinitionPermissionArgs( + actions=["*"], + not_actions=[], + )], + assignable_scopes=[primary.id]) + +role_assignment = authorization.RoleAssignment(f'{config.resource_name_prefix}-roleAssignment', + role_definition_id=role_definition.id, + principal_id=ad_sp.id, + principal_type='ServicePrincipal', + scope=acr.id) + +k8s_cluster = containerservice.ManagedCluster(f'{config.resource_name_prefix}-aks', + resource_group_name=resource_group.name, + tags=config.default_tags, + addon_profiles={ + }, + agent_pool_profiles=[{ + 'count': config.node_count, + 'max_pods': 110, + 'mode': 'System', + 'name': 'agentpool', + 'node_labels': {}, + 'os_disk_size_gb': 30, + 'os_type': 'Linux', + 'type': 'VirtualMachineScaleSets', + 'vm_size': config.node_size, + }], + dns_prefix=resource_group.name, + enable_rbac=True, + kubernetes_version=config.k8s_version, + linux_profile={ + 'admin_username': config.admin_username, + 'ssh': { + 'publicKeys': [{ + 'keyData': config.ssh_public_key, + }], + }, + }, + node_resource_group='node-resource-group', + service_principal_profile={ + 'client_id': ad_app.application_id, + 'secret': ad_sp_password.value, + }) + +creds = pulumi.Output.all(resource_group.name, k8s_cluster.name).apply( + lambda args: + containerservice.list_managed_cluster_user_credentials( + resource_group_name=args[0], + resource_name=args[1])) + +kubeconfig = creds.kubeconfigs[0].value.apply( + lambda enc: base64.b64decode(enc).decode()) + +k8s_provider = k8s.Provider('k8s-provider', kubeconfig=kubeconfig) diff --git a/azure-py-aks-acr-keda/config.py b/azure-py-aks-acr-keda/config.py new file mode 100644 index 000000000..27b88a584 --- /dev/null +++ b/azure-py-aks-acr-keda/config.py @@ -0,0 +1,34 @@ +from pulumi import Config, get_stack, get_project +from pulumi_random import RandomPassword +from pulumi_tls import PrivateKey + +config = Config() +project = get_project() +stack = get_stack() +resource_name_prefix = config.get('name_prefix') or project + +default_tags = { + 'manager': 'pulumi', + 'project': project, + 'stack': stack, + 'prefix': resource_name_prefix +} + +acr_name = resource_name_prefix.replace('-', '') + +k8s_version = config.get('k8sVersion') or '1.19.11' + +password = config.get('password') or RandomPassword('pw', + length=20, special=True) + +generated_key_pair = PrivateKey('ssh-key', + algorithm='RSA', rsa_bits=4096) + +admin_username = config.get('adminUserName') or 'testuser' + +ssh_public_key = config.get('sshPublicKey') or \ + generated_key_pair.public_key_openssh + +node_count = config.get_int('nodeCount') or 1 + +node_size = config.get('nodeSize') or 'standard_D2as_v4' diff --git a/azure-py-aks-acr-keda/requirements.txt b/azure-py-aks-acr-keda/requirements.txt new file mode 100644 index 000000000..8ccfb6705 --- /dev/null +++ b/azure-py-aks-acr-keda/requirements.txt @@ -0,0 +1,7 @@ +pulumi-azure-native>=1.0.0,<2.0.0 +pulumi-azuread>=4.0.0,<5.0.0 +pulumi-azure-native>=1.0.1, <2.0.0 +pulumi-kubernetes>=3.0.0,<4.0.0 +pulumi-random>=4.0.0,<5.0.0 +pulumi-tls>=4.0.0,<5.0.0 +pulumi>=3.0.0,<4.0.0 \ No newline at end of file