From bf3379348349656ddff5be4a9301f1fb04d5db6f Mon Sep 17 00:00:00 2001 From: modridi Date: Mon, 18 Sep 2023 15:42:53 +0200 Subject: [PATCH] feat: add azure example --- .../ROOT/pages/tutorials/deploy_aks.adoc | 65 +++++- examples/aks/.terraform-version | 1 + examples/aks/README.md | 0 examples/aks/application.tf | 54 +++++ examples/aks/base.tf | 10 + examples/aks/cluster.tf | 26 +++ examples/aks/dns.tf | 12 ++ examples/aks/locals.tf | 14 ++ examples/aks/network.tf | 13 ++ examples/aks/outputs.tf | 3 + examples/aks/providers.tf | 6 + examples/aks/stack.tf | 196 ++++++++++++++++++ examples/aks/state.tf | 17 ++ examples/aks/storage.tf | 23 ++ examples/aks/terraform.tf | 41 ++++ examples/aks/variables.tf | 0 16 files changed, 480 insertions(+), 1 deletion(-) create mode 100644 examples/aks/.terraform-version create mode 100644 examples/aks/README.md create mode 100644 examples/aks/application.tf create mode 100644 examples/aks/base.tf create mode 100644 examples/aks/cluster.tf create mode 100644 examples/aks/dns.tf create mode 100644 examples/aks/locals.tf create mode 100644 examples/aks/network.tf create mode 100644 examples/aks/outputs.tf create mode 100644 examples/aks/providers.tf create mode 100644 examples/aks/stack.tf create mode 100644 examples/aks/state.tf create mode 100644 examples/aks/storage.tf create mode 100644 examples/aks/terraform.tf create mode 100644 examples/aks/variables.tf diff --git a/docs/modules/ROOT/pages/tutorials/deploy_aks.adoc b/docs/modules/ROOT/pages/tutorials/deploy_aks.adoc index 361caa842d..bf191e271c 100644 --- a/docs/modules/ROOT/pages/tutorials/deploy_aks.adoc +++ b/docs/modules/ROOT/pages/tutorials/deploy_aks.adoc @@ -1,3 +1,66 @@ = Deployment On Azure AKS -_Work In Progress_ +This tutorial shows how to deploy a DevOps Stack instance on Azure Cloud. + +== Prerequisites + +* Azure CLI installed (version ~>2) +* An Azure account with an active subscription. +* "Application Developer" Azure (Active Directory) AD role assignment on the Azure AD instance the subscription trusts. +* "Owner" Azure role assignment on the subscription. + +== Login to Azure from CLI + +[source,bash] +---- +az login +az account set --subscription +---- + +== State file + +Throughout this tutorial, Terraform state file will be stored in an Azure Blob container of an Azure storage account. In order to do this, you need to create the remote state store first, so you'll work with a local state at the beginning. +First, Terraform apply the content of the file state.tf to create the container, the storage account and the resource group the account belongs to. +Once these resources created, you'll add the following block to terraform.tf and set the values from the previously created resources. + +[source,hcl] +---- +terraform { + backend "azurerm" { + resource_group_name = "" + storage_account_name = "" + container_name = "" + key = "tfstate" + } + ... +} +---- + +Then, Terraform apply and you'll be prompted to migrate the statefile, type yes. + +== DNS + +Since Azure can't be used to buy a domain name, you need to create a DNS zone in the suscription for managing DNS records (next step) and have a https://learn.microsoft.com/en-us/azure/dns/dns-domain-delegation[delegation] set up. + +== Provisions and cluster + +Before deploying the DevOps stack components, create all the resources these components require. +Comment the content of stack.tf (where DevOps Stack modules are declared) and Terraform apply. + +== Stack + +Terraform apply stack.tf + +== Login to ArgoCD + +Use `argocd_url` output to login to ArgoCD UI. +Before you login using OIDC, make sure you assign yourself ArgoCD App admin role. The following steps allow to do so: + +* Login to Azure portal and go to Azure Active Directory +* In the menu on the left, click `Enterprise applications` +* Select your application and go to `Users and groups` +* Add yourself as a user with `ArgoCD Administrator` role + +== k9s/kubectl + +To use kubectl, you need to login to Azure from CLI and select your subscription. diff --git a/examples/aks/.terraform-version b/examples/aks/.terraform-version new file mode 100644 index 0000000000..95b25aee25 --- /dev/null +++ b/examples/aks/.terraform-version @@ -0,0 +1 @@ +1.3.6 diff --git a/examples/aks/README.md b/examples/aks/README.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/examples/aks/application.tf b/examples/aks/application.tf new file mode 100644 index 0000000000..03c7ddd537 --- /dev/null +++ b/examples/aks/application.tf @@ -0,0 +1,54 @@ +resource "azuread_application" "this" { + display_name = format("devops-stack-apps-%s", local.platform_name) + + required_resource_access { + resource_app_id = "00000003-0000-0000-c000-000000000000" + + resource_access { + id = "e1fe6dd8-ba31-4d61-89e7-88639da4683d" + type = "Scope" + } + } + + optional_claims { + access_token { + additional_properties = [] + essential = false + name = "groups" + } + id_token { + additional_properties = [] + essential = false + name = "groups" + } + } + + web { + redirect_uris = [ + format("https://argocd.apps.%s.%s/auth/callback", local.cluster_name, azurerm_dns_zone.this.name), + format("https://grafana.apps.%s.%s/login/generic_oauth", local.cluster_name, azurerm_dns_zone.this.name), + format("https://prometheus.apps.%s.%s/oauth2/callback", local.cluster_name, azurerm_dns_zone.this.name), + format("https://alertmanager.apps.%s.%s/oauth2/callback", local.cluster_name, azurerm_dns_zone.this.name), + format("https://thanos-bucketweb.apps.%s.%s/oauth2/callback", local.cluster_name, azurerm_dns_zone.this.name), + format("https://thanos-query.apps.%s.%s/oauth2/callback", local.cluster_name, azurerm_dns_zone.this.name), + ] + } + + app_role { + allowed_member_types = ["User"] + description = "ArgoCD Admins" + display_name = "ArgoCD Administrator" + enabled = true + id = random_uuid.argocd_app_role_admin.result + value = "argocd-admin" + } + + group_membership_claims = ["ApplicationGroup"] +} + +resource "random_uuid" "argocd_app_role_admin" { +} + +resource "azuread_application_password" "this" { + application_object_id = azuread_application.this.object_id +} diff --git a/examples/aks/base.tf b/examples/aks/base.tf new file mode 100644 index 0000000000..2391e9cb2e --- /dev/null +++ b/examples/aks/base.tf @@ -0,0 +1,10 @@ +data "azuread_client_config" "current" { +} + +data "azurerm_client_config" "current" { +} + +resource "azurerm_resource_group" "default" { + name = "devops-stack" + location = "France Central" +} diff --git a/examples/aks/cluster.tf b/examples/aks/cluster.tf new file mode 100644 index 0000000000..a46ebaf7bb --- /dev/null +++ b/examples/aks/cluster.tf @@ -0,0 +1,26 @@ +module "cluster" { + source = "Azure/aks/azurerm" + version = "~> 6.0" + + kubernetes_version = 1.25 + orchestrator_version = 1.25 + prefix = local.cluster_name + vnet_subnet_id = azurerm_subnet.this.id + resource_group_name = azurerm_resource_group.default.name + azure_policy_enabled = true + network_plugin = "azure" + private_cluster_enabled = false + rbac_aad_managed = true + role_based_access_control_enabled = true + log_analytics_workspace_enabled = false + sku_tier = "Free" + agents_pool_name = "default" + agents_labels = { "devops-stack/nodepool" : "default" } + agents_count = 1 + agents_size = "Standard_D4s_v3" + agents_max_pods = 150 + os_disk_size_gb = 128 + oidc_issuer_enabled = true +} + +# TODO add cluster admin role assignment diff --git a/examples/aks/dns.tf b/examples/aks/dns.tf new file mode 100644 index 0000000000..cd8e278795 --- /dev/null +++ b/examples/aks/dns.tf @@ -0,0 +1,12 @@ +resource "azurerm_dns_zone" "this" { + name = "hello-ds.camptocamp.com" + resource_group_name = azurerm_resource_group.default.name +} + +resource "azurerm_dns_cname_record" "wildcard" { + name = "*.apps" + zone_name = azurerm_dns_zone.this.name + resource_group_name = azurerm_resource_group.default.name + ttl = 300 + record = format("%s-%s.%s.cloudapp.azure.com.", local.cluster_name, replace(azurerm_dns_zone.this.name, ".", "-"), azurerm_resource_group.default.location) +} diff --git a/examples/aks/locals.tf b/examples/aks/locals.tf new file mode 100644 index 0000000000..b5fc666954 --- /dev/null +++ b/examples/aks/locals.tf @@ -0,0 +1,14 @@ +locals { + platform_name = "example" + cluster_name = "blue" + + oidc = { + issuer_url = format("https://login.microsoftonline.com/%s/v2.0", data.azurerm_client_config.current.tenant_id) + oauth_url = format("https://login.microsoftonline.com/%s/oauth2/authorize", data.azurerm_client_config.current.tenant_id) + token_url = format("https://login.microsoftonline.com/%s/oauth2/token", data.azurerm_client_config.current.tenant_id) + api_url = format("https://graph.microsoft.com/oidc/userinfo") + client_id = azuread_application.this.application_id + client_secret = azuread_application_password.this.value + oauth2_proxy_extra_args = [] + } +} diff --git a/examples/aks/network.tf b/examples/aks/network.tf new file mode 100644 index 0000000000..73613fa6c2 --- /dev/null +++ b/examples/aks/network.tf @@ -0,0 +1,13 @@ +resource "azurerm_virtual_network" "this" { + name = "devops-stack-vnet" + resource_group_name = azurerm_resource_group.default.name + location = azurerm_resource_group.default.location + address_space = ["10.1.0.0/16"] +} + +resource "azurerm_subnet" "this" { + name = local.cluster_name + resource_group_name = azurerm_resource_group.default.name + address_prefixes = ["10.1.0.0/20"] + virtual_network_name = azurerm_virtual_network.this.name +} diff --git a/examples/aks/outputs.tf b/examples/aks/outputs.tf new file mode 100644 index 0000000000..b22669bb37 --- /dev/null +++ b/examples/aks/outputs.tf @@ -0,0 +1,3 @@ +output "argocd_url" { + value = format("https://argocd.apps.%s.%s", local.cluster_name, azurerm_dns_zone.this.name) +} diff --git a/examples/aks/providers.tf b/examples/aks/providers.tf new file mode 100644 index 0000000000..24f8278b07 --- /dev/null +++ b/examples/aks/providers.tf @@ -0,0 +1,6 @@ +provider "azurerm" { + features {} +} + +provider "azuread" { +} diff --git a/examples/aks/stack.tf b/examples/aks/stack.tf new file mode 100644 index 0000000000..82ed0a07a1 --- /dev/null +++ b/examples/aks/stack.tf @@ -0,0 +1,196 @@ +provider "helm" { + kubernetes { + host = module.cluster.admin_host + cluster_ca_certificate = base64decode(module.cluster.admin_cluster_ca_certificate) + client_key = base64decode(module.cluster.admin_client_key) + client_certificate = base64decode(module.cluster.admin_client_certificate) + username = module.cluster.admin_username + password = module.cluster.admin_password + } +} + +provider "kubernetes" { + host = module.cluster.admin_host + cluster_ca_certificate = base64decode(module.cluster.admin_cluster_ca_certificate) + client_key = base64decode(module.cluster.admin_client_key) + client_certificate = base64decode(module.cluster.admin_client_certificate) + username = module.cluster.admin_username + password = module.cluster.admin_password +} + +module "argocd_bootstrap" { + source = "git::https://github.com/camptocamp/devops-stack-module-argocd.git//bootstrap?ref=v1.1.0" +} + +provider "argocd" { + server_addr = "127.0.0.1:8080" + auth_token = module.argocd_bootstrap.argocd_auth_token + insecure = true + plain_text = true + port_forward = true + port_forward_with_namespace = module.argocd_bootstrap.argocd_namespace + + kubernetes { + host = module.cluster.admin_host + cluster_ca_certificate = base64decode(module.cluster.admin_cluster_ca_certificate) + client_key = base64decode(module.cluster.admin_client_key) + client_certificate = base64decode(module.cluster.admin_client_certificate) + } +} + +module "ingress" { + source = "git::https://github.com/camptocamp/devops-stack-module-traefik.git//aks?ref=v1.2.1" + + cluster_name = local.cluster_name + base_domain = azurerm_dns_zone.this.name + argocd_namespace = module.argocd_bootstrap.argocd_namespace + + enable_service_monitor = false + node_resource_group_name = module.cluster.node_resource_group + dns_zone_resource_group_name = azurerm_resource_group.default.name +} + +module "azure-workload-identity" { + source = "git::https://github.com/camptocamp/devops-stack-module-azure-workload-identity.git?ref=v0.1.0" + + argocd_namespace = module.argocd_bootstrap.argocd_namespace + + azure_tenant_id = data.azuread_client_config.current.tenant_id +} + +module "cert-manager" { + source = "git::https://github.com/camptocamp/devops-stack-module-cert-manager.git//aks?ref=v4.0.0" + + cluster_name = local.cluster_name + base_domain = azurerm_dns_zone.this.name + argocd_namespace = module.argocd_bootstrap.argocd_namespace + + cluster_oidc_issuer_url = module.cluster.oidc_issuer_url + node_resource_group_name = module.cluster.node_resource_group + dns_zone_resource_group_name = azurerm_resource_group.default.name + enable_service_monitor = false + + dependency_ids = { + azure-workload-identity = module.azure-workload-identity.id + } +} + +module "loki-stack" { + source = "git::https://github.com/camptocamp/devops-stack-module-loki-stack.git//aks?ref=v2.2.0" + + argocd_namespace = module.argocd_bootstrap.argocd_namespace + + distributed_mode = true + + logs_storage = { + container = azurerm_storage_container.logs.name + storage_account = azurerm_storage_account.this.name + managed_identity_node_rg_name = module.cluster.node_resource_group + managed_identity_oidc_issuer_url = module.cluster.oidc_issuer_url + } + + dependency_ids = { + azure-workload-identity = module.azure-workload-identity.id + } +} + +module "thanos" { + source = "git::https://github.com/camptocamp/devops-stack-module-thanos.git//aks?ref=v1.0.0" + + cluster_name = local.cluster_name + base_domain = azurerm_dns_zone.this.name + argocd_namespace = module.argocd_bootstrap.argocd_namespace + cluster_issuer = local.cluster_issuer + + metrics_storage = { + container = azurerm_storage_container.metrics.name + storage_account = azurerm_storage_account.this.name + storage_account_key = azurerm_storage_account.this.primary_access_key + } + + thanos = { + oidc = local.oidc + } + + dependency_ids = { + cert-manager = module.cert-manager.id + } +} + +module "kube-prometheus-stack" { + source = "git::https://github.com/camptocamp/devops-stack-module-kube-prometheus-stack.git//aks?ref=v2.3.0" + + cluster_name = local.cluster_name + base_domain = azurerm_dns_zone.this.name + argocd_namespace = module.argocd_bootstrap.argocd_namespace + cluster_issuer = "letsencrypt-staging" + + alertmanager = { + oidc = local.oidc + } + prometheus = { + oidc = local.oidc + } + grafana = { + oidc = local.oidc + additional_data_sources = true + } + + metrics_storage = { + container = azurerm_storage_container.metrics.name + storage_account = azurerm_storage_account.this.name + storage_account_key = azurerm_storage_account.this.primary_access_key + } + + dependency_ids = { + argocd = module.argocd_bootstrap.id + traefik = module.traefik.id + cert-manager = module.cert-manager.id + loki-stack = module.loki-stack.id + } +} + +module "argocd_final" { + source = "git::https://github.com/camptocamp/devops-stack-module-argocd.git?ref=v1.1.0" + + cluster_name = local.cluster_name + base_domain = azurerm_dns_zone.this.name + cluster_issuer = "letsencrypt-staging" + + admin_enabled = "true" + namespace = module.argocd_bootstrap.argocd_namespace + accounts_pipeline_tokens = module.argocd_bootstrap.argocd_accounts_pipeline_tokens + server_secretkey = module.argocd_bootstrap.argocd_server_secretkey + + oidc = { + name = "OIDC" + issuer = local.oidc.issuer_url + clientID = local.oidc.client_id + clientSecret = local.oidc.client_secret + requestedScopes = ["openid", "profile", "email"] + requestedIDTokenClaims = { + groups = { + essential = true + } + } + } + + helm_values = [{ + argo-cd = { + configs = { + rbac = { + "policy.csv" = <<-EOT + g, pipeline, role:admin + g, argocd-admin, role:admin + EOT + } + } + } + }] + + dependency_ids = { + traefik = module.traefik.id + cert-manager = module.cert-manager.id + kube-prometheus-stack = module.kube-prometheus-stack.id + } +} diff --git a/examples/aks/state.tf b/examples/aks/state.tf new file mode 100644 index 0000000000..4252a3e54b --- /dev/null +++ b/examples/aks/state.tf @@ -0,0 +1,17 @@ +resource "azurerm_resource_group" "state" { + name = "state-file" + location = "France Central" +} + +resource "azurerm_storage_account" "state" { + name = "dstackazstate" + resource_group_name = azurerm_resource_group.state.name + location = azurerm_resource_group.state.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_storage_container" "state" { + name = "state" + storage_account_name = azurerm_storage_account.state.name +} diff --git a/examples/aks/storage.tf b/examples/aks/storage.tf new file mode 100644 index 0000000000..9741fcfd3b --- /dev/null +++ b/examples/aks/storage.tf @@ -0,0 +1,23 @@ +resource "random_string" "storage_account_name" { + length = 8 + upper = false + special = false +} + +resource "azurerm_storage_account" "this" { + name = "dsstore${random_string.storage_account_name.result}" + resource_group_name = azurerm_resource_group.default.name + location = azurerm_resource_group.default.location + account_tier = "Standard" + account_replication_type = "LRS" +} + +resource "azurerm_storage_container" "logs" { + name = "logs" + storage_account_name = azurerm_storage_account.this.name +} + +resource "azurerm_storage_container" "metrics" { + name = "metrics" + storage_account_name = azurerm_storage_account.this.name +} diff --git a/examples/aks/terraform.tf b/examples/aks/terraform.tf new file mode 100644 index 0000000000..9facf3a84e --- /dev/null +++ b/examples/aks/terraform.tf @@ -0,0 +1,41 @@ +terraform { + backend "azurerm" { + resource_group_name = "state-file" + storage_account_name = "dstackazstate" + container_name = "state" + key = "tfstate" + } + + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "~> 3" + } + azuread = { + source = "hashicorp/azuread" + version = "~> 2" + } + jwt = { + source = "camptocamp/jwt" + version = ">= 0.0.3" + } + kubernetes = { + source = "hashicorp/kubernetes" + version = "~> 2" + } + argocd = { + source = "oboukili/argocd" + version = "~> 4" + } + htpasswd = { + source = "loafoe/htpasswd" + version = ">= 0.9" + } + random = { + source = "hashicorp/random" + version = ">= 3" + } + } + + required_version = ">= 1.2.0" +} diff --git a/examples/aks/variables.tf b/examples/aks/variables.tf new file mode 100644 index 0000000000..e69de29bb2