From 092897e4e3c8363c114d9201d993e4d46ab8a172 Mon Sep 17 00:00:00 2001 From: abhay652 Date: Sat, 5 Sep 2026 12:46:16 +0530 Subject: [PATCH 1/5] 8.1P: add Terraform infrastructure and trigger CI pipeline --- terraform/.gitignore | 5 ++ terraform/container_registry.tf | 15 ++++++ terraform/kubernetes_service.tf | 35 +++++++++++++ terraform/output.tf | 57 +++++++++++++++++++++ terraform/resource_group.tf | 11 +++++ terraform/storage_account.tf | 30 +++++++++++ terraform/terraform.tfvars | 30 +++++++++++ terraform/variables.tf | 88 +++++++++++++++++++++++++++++++++ terraform/versions.tf | 14 ++++++ 9 files changed, 285 insertions(+) create mode 100644 terraform/.gitignore create mode 100644 terraform/container_registry.tf create mode 100644 terraform/kubernetes_service.tf create mode 100644 terraform/output.tf create mode 100644 terraform/resource_group.tf create mode 100644 terraform/storage_account.tf create mode 100644 terraform/terraform.tfvars create mode 100644 terraform/variables.tf create mode 100644 terraform/versions.tf diff --git a/terraform/.gitignore b/terraform/.gitignore new file mode 100644 index 00000000..ec0afb96 --- /dev/null +++ b/terraform/.gitignore @@ -0,0 +1,5 @@ +*.tfstate +*.tfstate.* +.terraform/ +.terraform.lock.hcl +crash.log diff --git a/terraform/container_registry.tf b/terraform/container_registry.tf new file mode 100644 index 00000000..dc32f38a --- /dev/null +++ b/terraform/container_registry.tf @@ -0,0 +1,15 @@ +resource "azurerm_container_registry" "acr" { + name = var.acr_name + resource_group_name = azurerm_resource_group.rg.name + location = azurerm_resource_group.rg.location + + sku = "Basic" + admin_enabled = true + + tags = merge( + var.tags, + { + Environment = var.environment + } + ) +} \ No newline at end of file diff --git a/terraform/kubernetes_service.tf b/terraform/kubernetes_service.tf new file mode 100644 index 00000000..4547c738 --- /dev/null +++ b/terraform/kubernetes_service.tf @@ -0,0 +1,35 @@ +# Week 08: node_count is driven by var.aks_node_count, which defaults to 3. +resource "azurerm_kubernetes_cluster" "aks" { + name = var.aks_cluster_name + location = azurerm_resource_group.rg.location + resource_group_name = azurerm_resource_group.rg.name + dns_prefix = var.aks_dns_prefix + kubernetes_version = var.kubernetes_version + + default_node_pool { + name = "default" + node_count = var.aks_node_count + vm_size = var.aks_node_vm_size + } + + identity { + type = "SystemAssigned" + } + + tags = merge( + var.tags, + { + Environment = var.environment + } + ) +} + +# +# Grant AKS permission to pull images from your ACR +# +resource "azurerm_role_assignment" "acr_pull" { + principal_id = azurerm_kubernetes_cluster.aks.kubelet_identity[0].object_id + role_definition_name = "AcrPull" + scope = azurerm_container_registry.acr.id + skip_service_principal_aad_check = true +} \ No newline at end of file diff --git a/terraform/output.tf b/terraform/output.tf new file mode 100644 index 00000000..36e88828 --- /dev/null +++ b/terraform/output.tf @@ -0,0 +1,57 @@ +output "resource_group_name" { + description = "Name of the resource group" + value = azurerm_resource_group.rg.name +} + +output "acr_name" { + description = "Name of the Azure Container Registry" + value = azurerm_container_registry.acr.name +} + +output "acr_login_server" { + description = "Login server of the Azure Container Registry" + value = azurerm_container_registry.acr.login_server +} + +output "storage_account_name" { + description = "Name of the Azure Storage Account" + value = azurerm_storage_account.storage_account.name +} + +output "storage_connection_string" { + description = "Connection string used by the application to access Blob Storage" + value = azurerm_storage_account.storage_account.primary_connection_string + sensitive = true +} + +output "student_profile_container" { + description = "Student profile photo Blob container" + value = azurerm_storage_container.student_profile_photo.name +} + +output "lecturer_profile_container" { + description = "Lecturer profile photo Blob container" + value = azurerm_storage_container.lecturer_profile_photo.name +} + +output "aks_cluster_name" { + description = "Name of the AKS cluster" + value = azurerm_kubernetes_cluster.aks.name +} + +output "aks_get_credentials_command" { + description = "Azure CLI command used to configure kubectl" + value = join(" ", [ + "az aks get-credentials", + "--resource-group", + azurerm_resource_group.rg.name, + "--name", + azurerm_kubernetes_cluster.aks.name, + "--overwrite-existing" + ]) +} + +output "acr_login_command" { + description = "Azure CLI command used to log in to ACR" + value = "az acr login --name ${azurerm_container_registry.acr.name}" +} \ No newline at end of file diff --git a/terraform/resource_group.tf b/terraform/resource_group.tf new file mode 100644 index 00000000..ff3255ec --- /dev/null +++ b/terraform/resource_group.tf @@ -0,0 +1,11 @@ +resource "azurerm_resource_group" "rg" { + name = var.resource_group_name + location = var.location + + tags = merge( + var.tags, + { + Environment = var.environment + } + ) +} \ No newline at end of file diff --git a/terraform/storage_account.tf b/terraform/storage_account.tf new file mode 100644 index 00000000..ce33235f --- /dev/null +++ b/terraform/storage_account.tf @@ -0,0 +1,30 @@ +resource "azurerm_storage_account" "storage_account" { + name = var.storage_account_name + resource_group_name = azurerm_resource_group.rg.name + location = azurerm_resource_group.rg.location + + account_tier = "Standard" + account_replication_type = "LRS" + + min_tls_version = "TLS1_2" + allow_nested_items_to_be_public = false + + tags = merge( + var.tags, + { + Environment = var.environment + } + ) +} + +resource "azurerm_storage_container" "student_profile_photo" { + name = "student-profile-photo" + storage_account_id = azurerm_storage_account.storage_account.id + container_access_type = "private" +} + +resource "azurerm_storage_container" "lecturer_profile_photo" { + name = "lecturer-profile-photo" + storage_account_id = azurerm_storage_account.storage_account.id + container_access_type = "private" +} \ No newline at end of file diff --git a/terraform/terraform.tfvars b/terraform/terraform.tfvars new file mode 100644 index 00000000..feb76208 --- /dev/null +++ b/terraform/terraform.tfvars @@ -0,0 +1,30 @@ +location = "Australia East" +resource_group_name = "koalatech-week08-rg" + +# Globally unique, alphanumeric only, lowercase +acr_name = "koalatechacrs225273076" + +# Globally unique, 3-24 lowercase letters and numbers +storage_account_name = "koalatechsts225273076" + +aks_cluster_name = "koalatech-aks-s225273076" +aks_dns_prefix = "koalatech" + +# Week 08 requires 3 nodes: staging and production both run +# persistent PostgreSQL workloads on the same cluster. +aks_node_count = 3 +aks_node_vm_size = "Standard_D2s_v3" + +# Leave kubernetes_version unset so Azure picks the regional default. +# To pin a version, check what is available first: +# az aks get-versions --location australiaeast --output table +# kubernetes_version = "1.31.2" + +environment = "development" + +tags = { + Project = "KoalaTech Course Platform" + ManagedBy = "Terraform" + Practical = "Week08" + Environment = "Development" +} diff --git a/terraform/variables.tf b/terraform/variables.tf new file mode 100644 index 00000000..426899b0 --- /dev/null +++ b/terraform/variables.tf @@ -0,0 +1,88 @@ +variable "location" { + description = "Azure region where the resources will be created" + type = string + default = "Australia East" +} + +variable "resource_group_name" { + description = "Name of the Azure Resource Group" + type = string +} + +variable "acr_name" { + description = "Globally unique name of the Azure Container Registry" + type = string + + validation { + condition = can(regex("^[a-zA-Z0-9]+$", var.acr_name)) + error_message = "The ACR name must contain only alphanumeric characters." + } +} + +variable "storage_account_name" { + description = "Globally unique name of the Azure Storage Account" + type = string + + validation { + condition = ( + length(var.storage_account_name) >= 3 && + length(var.storage_account_name) <= 24 && + can(regex("^[a-z0-9]+$", var.storage_account_name)) + ) + + error_message = "The storage account name must contain 3–24 lowercase letters and numbers." + } +} + +variable "aks_cluster_name" { + description = "Name of the Azure Kubernetes Service cluster" + type = string +} + +variable "aks_dns_prefix" { + description = "DNS prefix used by the AKS cluster" + type = string +} + +variable "aks_node_count" { + description = "Number of nodes in the default AKS node pool" + type = number + + # Week 08 requires 3 nodes: staging and production both run + # persistent PostgreSQL workloads on the same cluster. + default = 3 + + validation { + condition = var.aks_node_count >= 1 + error_message = "The AKS node count must be at least 1." + } +} + +variable "aks_node_vm_size" { + description = "Virtual machine size used by the AKS nodes" + type = string + default = "Standard_D2s_v3" +} + +variable "environment" { + description = "Environment name applied to resource tags" + type = string + default = "development" +} + +variable "kubernetes_version" { + description = "AKS Kubernetes version. Leave null to let Azure choose the default for the region. Check available versions with: az aks get-versions --location australiaeast --output table" + type = string + default = null +} + +variable "tags" { + description = "Tags applied to Azure resources" + type = map(string) + + default = { + Project = "KoalaTech Course Platform" + ManagedBy = "Terraform" + Practical = "Week08" + } +} \ No newline at end of file diff --git a/terraform/versions.tf b/terraform/versions.tf new file mode 100644 index 00000000..47437408 --- /dev/null +++ b/terraform/versions.tf @@ -0,0 +1,14 @@ +terraform { + required_version = ">= 1.7.0" + + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "~> 4.0" + } + } +} + +provider "azurerm" { + features {} +} \ No newline at end of file From 0cd1b68012e91402f73cc4b42babd7df00e69ac8 Mon Sep 17 00:00:00 2001 From: abhay652 Date: Wed, 16 Sep 2026 12:18:46 +0530 Subject: [PATCH 2/5] Redeploy to rebuilt AKS cluster for Task 9.1P From 916a54108ea7bb70c4ae4b76e5b8bb930694518b Mon Sep 17 00:00:00 2001 From: abhay652 Date: Wed, 16 Sep 2026 14:08:12 +0530 Subject: [PATCH 3/5] Enable continuous deployment to production after staging tests pass --- .github/workflows/04-deploy-production.yml | 37 ++++++++++++++++++---- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/.github/workflows/04-deploy-production.yml b/.github/workflows/04-deploy-production.yml index 969d646b..f5c10b4c 100644 --- a/.github/workflows/04-deploy-production.yml +++ b/.github/workflows/04-deploy-production.yml @@ -1,6 +1,16 @@ name: 04 - Deploy to Production on: + # Continuous Deployment: run automatically after staging tests complete + workflow_run: + workflows: + - "03 - Test Staging" + types: + - completed + branches: + - main + + # Manual trigger kept for redeploying or rolling back a specific version workflow_dispatch: inputs: image_tag: @@ -8,15 +18,30 @@ on: required: true type: string +# Prevent two production deployments from running at the same time +concurrency: + group: production + cancel-in-progress: false + jobs: deploy-production: name: Deploy to Production runs-on: ubuntu-latest + # Only deploy if the staging tests passed, or if run manually + if: github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' + environment: name: production + # Use the SHA tested in staging, or the manual input if run by hand + env: + IMAGE_TAG: ${{ inputs.image_tag || github.event.workflow_run.head_sha }} + steps: + - name: Show image tag being deployed + run: echo "Deploying image tag ${{ env.IMAGE_TAG }} to production" + - name: Checkout repository uses: actions/checkout@v4 @@ -69,37 +94,37 @@ jobs: - name: Update frontend image run: | kubectl set image deployment/frontend \ - frontend=${{ vars.ACR_LOGIN_SERVER }}/koalatech-frontend:${{ inputs.image_tag }} \ + frontend=${{ vars.ACR_LOGIN_SERVER }}/koalatech-frontend:${{ env.IMAGE_TAG }} \ -n production - name: Update user-service image run: | kubectl set image deployment/user-service \ - user-service=${{ vars.ACR_LOGIN_SERVER }}/koalatech-user-service:${{ inputs.image_tag }} \ + user-service=${{ vars.ACR_LOGIN_SERVER }}/koalatech-user-service:${{ env.IMAGE_TAG }} \ -n production - name: Update student-service image run: | kubectl set image deployment/student-service \ - student-service=${{ vars.ACR_LOGIN_SERVER }}/koalatech-student-service:${{ inputs.image_tag }} \ + student-service=${{ vars.ACR_LOGIN_SERVER }}/koalatech-student-service:${{ env.IMAGE_TAG }} \ -n production - name: Update lecturer-service image run: | kubectl set image deployment/lecturer-service \ - lecturer-service=${{ vars.ACR_LOGIN_SERVER }}/koalatech-lecturer-service:${{ inputs.image_tag }} \ + lecturer-service=${{ vars.ACR_LOGIN_SERVER }}/koalatech-lecturer-service:${{ env.IMAGE_TAG }} \ -n production - name: Update course-service image run: | kubectl set image deployment/course-service \ - course-service=${{ vars.ACR_LOGIN_SERVER }}/koalatech-course-service:${{ inputs.image_tag }} \ + course-service=${{ vars.ACR_LOGIN_SERVER }}/koalatech-course-service:${{ env.IMAGE_TAG }} \ -n production - name: Update enrollment-service image run: | kubectl set image deployment/enrollment-service \ - enrollment-service=${{ vars.ACR_LOGIN_SERVER }}/koalatech-enrollment-service:${{ inputs.image_tag }} \ + enrollment-service=${{ vars.ACR_LOGIN_SERVER }}/koalatech-enrollment-service:${{ env.IMAGE_TAG }} \ -n production - name: Wait for frontend rollout From de05d65132a092828bd155d6d34e6707332bc25b Mon Sep 17 00:00:00 2001 From: abhay652 Date: Wed, 16 Sep 2026 14:42:45 +0530 Subject: [PATCH 4/5] Add release banner to login page to demonstrate continuous deployment --- frontend/src/pages/Login.jsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/frontend/src/pages/Login.jsx b/frontend/src/pages/Login.jsx index 1132741c..1ebf13a5 100644 --- a/frontend/src/pages/Login.jsx +++ b/frontend/src/pages/Login.jsx @@ -103,6 +103,14 @@ const Login = () => { Sign in to continue + + Release 2.0 – deployed automatically via Continuous Deployment + + {error && ( Date: Tue, 29 Sep 2026 14:43:38 +0530 Subject: [PATCH 5/5] 10.2D: add Terraform CI, Docker Scout scanning and Prometheus/Grafana monitoring --- .github/workflows/00-infra.yml | 50 +++++++++ .github/workflows/01-ci.yml | 30 ++++- .github/workflows/04-deploy-production.yml | 44 +++++++- monitoring/grafana-dashboard.json | 117 ++++++++++++++++++++ monitoring/servicemonitor-user-service.yaml | 26 +++++ monitoring/values-monitoring.yaml | 30 +++++ terraform/variables.tf | 4 + terraform/versions.tf | 15 +-- 8 files changed, 305 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/00-infra.yml create mode 100644 monitoring/grafana-dashboard.json create mode 100644 monitoring/servicemonitor-user-service.yaml create mode 100644 monitoring/values-monitoring.yaml diff --git a/.github/workflows/00-infra.yml b/.github/workflows/00-infra.yml new file mode 100644 index 00000000..3afe1696 --- /dev/null +++ b/.github/workflows/00-infra.yml @@ -0,0 +1,50 @@ +name: 00 - Infrastructure (Terraform) + +# Runs Terraform validation and planning in CI whenever the Terraform code +# changes, and can be triggered manually. This is the "Terraform integrated into +# the pipeline" evidence. `apply` is performed locally with elevated credentials +# (least privilege: the CI service principal is Contributor only). + +on: + push: + branches: [ main ] + paths: + - "terraform/**" + - ".github/workflows/00-infra.yml" + pull_request: + branches: [ main ] + paths: + - "terraform/**" + workflow_dispatch: + +jobs: + terraform: + name: Terraform validate & plan + runs-on: ubuntu-latest + defaults: + run: + working-directory: terraform + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Login to Azure + uses: azure/login@v2 + with: + creds: ${{ secrets.AZURE_CREDENTIALS }} + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + + - name: Terraform init (no backend) + run: terraform init -backend=false + + - name: Terraform format check + run: terraform fmt -check -recursive + continue-on-error: true + + - name: Terraform validate + run: terraform validate + + - name: Terraform plan + run: terraform plan -input=false \ No newline at end of file diff --git a/.github/workflows/01-ci.yml b/.github/workflows/01-ci.yml index 8206db43..2144c1d2 100644 --- a/.github/workflows/01-ci.yml +++ b/.github/workflows/01-ci.yml @@ -5,7 +5,7 @@ on: push: branches: - main - + # Manual trigger for the workflow workflow_dispatch: @@ -103,7 +103,7 @@ jobs: # ========================================================= - # Build and Push Docker Images + # Build, Scan and Push Docker Images # ========================================================= build-and-push: name: Build and Push ${{ matrix.image }} @@ -160,7 +160,31 @@ jobs: -t ${{ vars.ACR_LOGIN_SERVER }}/${{ matrix.image }}:${{ github.sha }} \ ./${{ matrix.service }} + # --------------------------------------------------------------- + # SECURITY SCANNING (Docker Scout) — runs AFTER build, BEFORE push + # so a vulnerable image is analysed before it enters the registry. + # Requires DOCKERHUB_USERNAME and DOCKERHUB_TOKEN repo secrets. + # --------------------------------------------------------------- + - name: Scan image with Docker Scout + uses: docker/scout-action@v1 + continue-on-error: true # report-only: never blocks the pipeline + with: + command: cves,recommendations + image: ${{ vars.ACR_LOGIN_SERVER }}/${{ matrix.image }}:${{ github.sha }} + only-severities: critical,high + sarif-file: scout-${{ matrix.image }}.sarif + dockerhub-user: ${{ secrets.DOCKERHUB_USERNAME }} + dockerhub-password: ${{ secrets.DOCKERHUB_TOKEN }} + # exit-code: true # uncomment to FAIL the build on critical/high CVEs + + - name: Upload Docker Scout SARIF (optional) + if: always() + uses: github/codeql-action/upload-sarif@v3 + continue-on-error: true + with: + sarif_file: scout-${{ matrix.image }}.sarif + - name: Push Docker image with commit SHA run: | docker push \ - ${{ vars.ACR_LOGIN_SERVER }}/${{ matrix.image }}:${{ github.sha }} + ${{ vars.ACR_LOGIN_SERVER }}/${{ matrix.image }}:${{ github.sha }} \ No newline at end of file diff --git a/.github/workflows/04-deploy-production.yml b/.github/workflows/04-deploy-production.yml index f5c10b4c..84a6c9d6 100644 --- a/.github/workflows/04-deploy-production.yml +++ b/.github/workflows/04-deploy-production.yml @@ -167,4 +167,46 @@ jobs: run: | kubectl get pods -n production kubectl get services -n production - kubectl get pvc -n production \ No newline at end of file + kubectl get pvc -n production + + # ========================================================= + # MONITORING & OBSERVABILITY (Prometheus + Grafana) + # Runs after a successful production deployment. + # ========================================================= + monitoring: + name: Deploy and validate monitoring + runs-on: ubuntu-latest + needs: deploy-production + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Login to Azure + uses: azure/login@v2 + with: + creds: ${{ secrets.AZURE_CREDENTIALS }} + + - name: Get AKS credentials + run: | + az aks get-credentials \ + --resource-group ${{ vars.AKS_RESOURCE_GROUP }} \ + --name ${{ vars.AKS_CLUSTER_NAME }} \ + --overwrite-existing + + - name: Install / upgrade kube-prometheus-stack + run: | + helm repo add prometheus-community https://prometheus-community.github.io/helm-charts + helm repo update + helm upgrade --install monitoring prometheus-community/kube-prometheus-stack \ + --namespace monitoring --create-namespace \ + -f monitoring/values-monitoring.yaml \ + --wait --timeout 10m + + - name: Apply application ServiceMonitor + run: kubectl apply -f monitoring/servicemonitor-user-service.yaml + + - name: Validate monitoring is running + run: | + kubectl get pods -n monitoring + kubectl get servicemonitors -A \ No newline at end of file diff --git a/monitoring/grafana-dashboard.json b/monitoring/grafana-dashboard.json new file mode 100644 index 00000000..1445ad2f --- /dev/null +++ b/monitoring/grafana-dashboard.json @@ -0,0 +1,117 @@ +{ + "__inputs": [ + { + "name": "DS_PROMETHEUS", + "label": "Prometheus", + "description": "", + "type": "datasource", + "pluginId": "prometheus", + "pluginName": "Prometheus" + } + ], + "annotations": { "list": [] }, + "editable": true, + "graphTooltip": 0, + "schemaVersion": 39, + "style": "dark", + "tags": ["sit722", "week10"], + "templating": { + "list": [ + { + "name": "datasource", + "type": "datasource", + "query": "prometheus", + "current": {}, + "hide": 0, + "label": "Data source" + }, + { + "name": "namespace", + "type": "query", + "datasource": "${datasource}", + "query": "label_values(kube_pod_info, namespace)", + "current": { "text": "default", "value": "default" }, + "refresh": 2, + "includeAll": false, + "label": "Namespace" + } + ] + }, + "time": { "from": "now-1h", "to": "now" }, + "timepicker": {}, + "title": "SIT722 Week08 App & Cluster", + "uid": "sit722-week10", + "version": 1, + "panels": [ + { + "type": "timeseries", + "title": "CPU usage by pod (cores)", + "datasource": "${datasource}", + "gridPos": { "h": 9, "w": 12, "x": 0, "y": 0 }, + "fieldConfig": { "defaults": { "unit": "short" }, "overrides": [] }, + "targets": [ + { + "datasource": "${datasource}", + "expr": "sum by (pod) (rate(container_cpu_usage_seconds_total{namespace=\"$namespace\", container!=\"\", container!=\"POD\"}[5m]))", + "legendFormat": "{{pod}}" + } + ] + }, + { + "type": "timeseries", + "title": "Memory working set by pod (bytes)", + "datasource": "${datasource}", + "gridPos": { "h": 9, "w": 12, "x": 12, "y": 0 }, + "fieldConfig": { "defaults": { "unit": "bytes" }, "overrides": [] }, + "targets": [ + { + "datasource": "${datasource}", + "expr": "sum by (pod) (container_memory_working_set_bytes{namespace=\"$namespace\", container!=\"\", container!=\"POD\"})", + "legendFormat": "{{pod}}" + } + ] + }, + { + "type": "stat", + "title": "Running pods", + "datasource": "${datasource}", + "gridPos": { "h": 6, "w": 6, "x": 0, "y": 9 }, + "fieldConfig": { "defaults": { "unit": "short" }, "overrides": [] }, + "targets": [ + { + "datasource": "${datasource}", + "expr": "count(kube_pod_status_phase{namespace=\"$namespace\", phase=\"Running\"} == 1)", + "legendFormat": "running" + } + ] + }, + { + "type": "timeseries", + "title": "Pod restarts (increase, 1h)", + "datasource": "${datasource}", + "gridPos": { "h": 6, "w": 18, "x": 6, "y": 9 }, + "fieldConfig": { "defaults": { "unit": "short" }, "overrides": [] }, + "targets": [ + { + "datasource": "${datasource}", + "expr": "sum by (pod) (increase(kube_pod_container_status_restarts_total{namespace=\"$namespace\"}[1h]))", + "legendFormat": "{{pod}}" + } + ] + }, + { + "type": "timeseries", + "title": "HTTP request rate (needs app /metrics — optional)", + "datasource": "${datasource}", + "gridPos": { "h": 9, "w": 24, "x": 0, "y": 15 }, + "fieldConfig": { "defaults": { "unit": "reqps" }, "overrides": [] }, + "targets": [ + { + "datasource": "${datasource}", + "expr": "sum by (handler) (rate(http_requests_total{namespace=\"$namespace\"}[5m]))", + "legendFormat": "{{handler}}" + } + ] + } + ] +} diff --git a/monitoring/servicemonitor-user-service.yaml b/monitoring/servicemonitor-user-service.yaml new file mode 100644 index 00000000..bd18f7d0 --- /dev/null +++ b/monitoring/servicemonitor-user-service.yaml @@ -0,0 +1,26 @@ +# ServiceMonitor: tells Prometheus to scrape user-service /metrics. +# +# NOTE: this only produces data if user-service actually exposes a /metrics +# endpoint. FastAPI does not by default — see the "Optional: app metrics" +# section of the setup guide to add it. Kubernetes/cluster metrics +# (CPU, memory, pods, nodes) are collected automatically WITHOUT this file, +# so your Grafana dashboard will show data either way. + +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + name: user-service + namespace: monitoring + labels: + release: monitoring # must match the Helm release name so it's picked up +spec: + namespaceSelector: + matchNames: + - default # namespace where user-service runs (change if different) + selector: + matchLabels: + app: user-service # must match the labels on your user-service Service + endpoints: + - port: http # the NAME of the port on the Service (not the number) + path: /metrics + interval: 15s diff --git a/monitoring/values-monitoring.yaml b/monitoring/values-monitoring.yaml new file mode 100644 index 00000000..02b07573 --- /dev/null +++ b/monitoring/values-monitoring.yaml @@ -0,0 +1,30 @@ +# Helm values for prometheus-community/kube-prometheus-stack +# Installs Prometheus, Grafana, Alertmanager, node-exporter and kube-state-metrics. + +grafana: + # Demo credentials — CHANGE THIS before submitting/sharing. + adminPassword: "ChangeMe123!" + service: + type: ClusterIP # access via `kubectl port-forward` (see setup guide) + +prometheus: + prometheusSpec: + # IMPORTANT: allows Prometheus to discover ServiceMonitors you create yourself + # (like servicemonitor-user-service.yaml), not only the chart's own ones. + serviceMonitorSelectorNilUsesHelmValues: false + podMonitorSelectorNilUsesHelmValues: false + retention: 6h + resources: + requests: + cpu: 200m + memory: 512Mi + limits: + memory: 1Gi + +# Keep the footprint small for a student cluster +alertmanager: + alertmanagerSpec: + resources: + requests: + cpu: 50m + memory: 128Mi diff --git a/terraform/variables.tf b/terraform/variables.tf index 426899b0..bc6bf19e 100644 --- a/terraform/variables.tf +++ b/terraform/variables.tf @@ -85,4 +85,8 @@ variable "tags" { ManagedBy = "Terraform" Practical = "Week08" } +} +variable "subscription_id" { + description = "Azure subscription ID" + type = string } \ No newline at end of file diff --git a/terraform/versions.tf b/terraform/versions.tf index 47437408..85cb5a9a 100644 --- a/terraform/versions.tf +++ b/terraform/versions.tf @@ -1,14 +1,15 @@ terraform { - required_version = ">= 1.7.0" + required_version = ">= 1.5.0" - required_providers { - azurerm = { - source = "hashicorp/azurerm" - version = "~> 4.0" - } + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "~> 4.0" } + } } provider "azurerm" { - features {} + features {} + subscription_id = var.subscription_id } \ No newline at end of file