From 7842398461888ac24763f63fcf802d7ef87ca9a4 Mon Sep 17 00:00:00 2001 From: Aayushree Sapkota Date: Sat, 19 Sep 2026 13:59:01 +1000 Subject: [PATCH 1/3] Configure Continuous Deployment in workflows and add Terraform configuration --- .github/workflows/01-ci.yml | 11 ++- .github/workflows/04-deploy-production.yml | 56 +++++++++++--- .gitignore | 15 +++- terraform/.terraform.lock.hcl | 22 ++++++ terraform/container_registry.tf | 15 ++++ terraform/kubernetes_serivce.tf | 34 +++++++++ terraform/output.tf | 57 ++++++++++++++ terraform/resource_group.tf | 11 +++ terraform/storage_account.tf | 30 ++++++++ terraform/terraform.tfvars | 24 ++++++ terraform/variables.tf | 89 ++++++++++++++++++++++ terraform/versions.tf | 27 +++++++ 12 files changed, 378 insertions(+), 13 deletions(-) create mode 100644 terraform/.terraform.lock.hcl create mode 100644 terraform/container_registry.tf create mode 100644 terraform/kubernetes_serivce.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/.github/workflows/01-ci.yml b/.github/workflows/01-ci.yml index 8206db43..3a99a894 100644 --- a/.github/workflows/01-ci.yml +++ b/.github/workflows/01-ci.yml @@ -6,6 +6,11 @@ on: branches: - main + # Trigger the workflow on pull requests targeting main branch + pull_request: + branches: + - main + # Manual trigger for the workflow workflow_dispatch: @@ -83,10 +88,10 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v7 with: python-version: "3.12" @@ -142,7 +147,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Login to Azure uses: azure/login@v2 diff --git a/.github/workflows/04-deploy-production.yml b/.github/workflows/04-deploy-production.yml index 969d646b..7352f1d0 100644 --- a/.github/workflows/04-deploy-production.yml +++ b/.github/workflows/04-deploy-production.yml @@ -1,11 +1,21 @@ name: 04 - Deploy to Production on: + # Automated trigger: runs automatically when Staging tests pass + workflow_run: + workflows: + - "03 - Test Staging" + types: + - completed + branches: + - main + + # Manual trigger fallback workflow_dispatch: inputs: image_tag: description: "Tested image SHA to deploy" - required: true + required: false type: string jobs: @@ -13,12 +23,29 @@ jobs: name: Deploy to Production runs-on: ubuntu-latest + # Only run if manual trigger OR if staging tests succeeded + if: > + ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }} + environment: name: production + env: + IMAGE_TAG: ${{ inputs.image_tag || github.event.workflow_run.head_sha || github.sha }} + steps: - - name: Checkout repository + - name: Checkout tested commit uses: actions/checkout@v4 + with: + ref: ${{ env.IMAGE_TAG }} + + - name: Display deployment metadata + run: | + echo "==========================================" + echo "Starting Continuous Deployment to Production" + echo "Trigger Event: ${{ github.event_name }}" + echo "Deploying Image Tag / Commit SHA: ${{ env.IMAGE_TAG }}" + echo "==========================================" - name: Login to Azure uses: azure/login@v3 @@ -69,37 +96,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 @@ -142,4 +169,15 @@ 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 + + - name: Show production frontend URL + run: | + echo "==========================================" + echo "Production Application Deployment Complete" + echo "==========================================" + kubectl get service frontend -n production + PROD_IP=$(kubectl get service frontend -n production -o jsonpath='{.status.loadBalancer.ingress[0].ip}') + echo "Production Application External IP: $PROD_IP" + echo "Production Application URL: http://${PROD_IP}" + echo "Deployed Image Tag: ${{ env.IMAGE_TAG }}" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4299cd1a..aa739d68 100644 --- a/.gitignore +++ b/.gitignore @@ -196,4 +196,17 @@ cython_debug/ .cursorignore .cursorindexingignore -node_modules/ \ No newline at end of file +node_modules/ + +# Terraform +.terraform/ +*.tfstate +*.tfstate.* +*.tfvars.json +crash.log +override.tf +override.tf.json +*_override.tf +*_override.tf.json +.terraformrc +terraform.rc \ No newline at end of file diff --git a/terraform/.terraform.lock.hcl b/terraform/.terraform.lock.hcl new file mode 100644 index 00000000..1c302601 --- /dev/null +++ b/terraform/.terraform.lock.hcl @@ -0,0 +1,22 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/azurerm" { + version = "4.81.0" + constraints = "~> 4.0" + hashes = [ + "h1:XhToZua4gtih1Kv8RdStcfND83G4Tmb6GZFT4jEUhDU=", + "zh:0732e7b74264ddfa2b90ba69d01c283d3cbae9f72ed3e506c6ac92529fed7fd3", + "zh:12afb524e232fe4e3d6161927724af5dfa4831d71edd9c174917ca9b7377bfae", + "zh:169d619ae202c4145e02fb706fb7c3679445ab3e3ff722edbf89597517a8c92e", + "zh:6beb95a3ef2f2d9c76abaa48e5450e90686a3fb6a47f1cb0ff7c5e94b6960151", + "zh:705e075fb5ffc4bf66fd7cbabf1a65007a41621e80030a2c158a4c83b6046216", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:79a8d17fefe647040fcb9ee8821a4f09f395427c4fd49493489b9a93a9a1038e", + "zh:8cc3f900b3774c0ae37ae42365c4579a199cf9e5edf88e476fdf5ab1048f84ea", + "zh:dec373b9390fa95e257291acd018ed65a7d512b428645d35e22cdbe8b245a08b", + "zh:e60f1e9fb45df6defade2855ed6e68547409ea75d30655c556adb0c08579749b", + "zh:f901d12ec82f3f8b5880a27b5cbcd7bd0d97e60c9367a2d7ed82fdd1157b39ff", + "zh:facf68ea5bf0f2b8ba720e7fba5f86492e1d4c591100460bb91c3f79f391f4b6", + ] +} 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_serivce.tf b/terraform/kubernetes_serivce.tf new file mode 100644 index 00000000..bee30484 --- /dev/null +++ b/terraform/kubernetes_serivce.tf @@ -0,0 +1,34 @@ +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..04263f2c --- /dev/null +++ b/terraform/terraform.tfvars @@ -0,0 +1,24 @@ +location = "Australia East" +resource_group_name = "koalatech-week08-rg" + +# Replace with a unique name for your Azure Container Registry +acr_name = "s225598173Week08Acr" + +# Replace with a unique name for your Azure Storage Account +storage_account_name = "s225598173storage" + +# Replace with a unique name for your Azure Kubernetes Service cluster +aks_cluster_name = "s225598173Week08Aks" +aks_dns_prefix = "koalatech" + +aks_node_count = 3 +aks_node_vm_size = "Standard_D2s_v3" + +environment = "development" + +tags = { + Project = "KoalaTech Course Platform" + ManagedBy = "Terraform" + Practical = "Week08-D" + Environment = "Development" +} \ No newline at end of file diff --git a/terraform/variables.tf b/terraform/variables.tf new file mode 100644 index 00000000..628ba9cd --- /dev/null +++ b/terraform/variables.tf @@ -0,0 +1,89 @@ +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 "image_tag" { + description = "Docker Image Tag" + type = string +} + + +variable "aks_node_count" { + description = "Number of nodes in the default AKS node pool" + type = number + default = 2 + + 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" { + default = "1.36.1" +} + +variable "tags" { + description = "Tags applied to Azure resources" + type = map(string) + + default = { + Project = "KoalaTech Course Platform" + ManagedBy = "Terraform" + Practical = "Week06" + } +} \ No newline at end of file diff --git a/terraform/versions.tf b/terraform/versions.tf new file mode 100644 index 00000000..94549d50 --- /dev/null +++ b/terraform/versions.tf @@ -0,0 +1,27 @@ +terraform { + required_version = ">= 1.7.0" + + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "~> 4.0" + } + } + + # REMOTE STATE BACKEND + backend "azurerm" { + resource_group_name = "tfstate-rg" + storage_account_name = "tfstate20245" + container_name = "tfstate" + key = "koalatech.tfstate" + } + +} + +provider "azurerm" { + features { + resource_group { + prevent_deletion_if_contains_resources = false + } + } +} \ No newline at end of file From 3b35b63a008b9be858a7d4b2f814ec9d0a29cc7d Mon Sep 17 00:00:00 2001 From: Aayushree Sapkota Date: Sat, 19 Sep 2026 13:59:43 +1000 Subject: [PATCH 2/3] Update frontend display with new header and teal theme for CD demonstration --- frontend/src/components/Header.jsx | 25 ++++++++++++++++--------- frontend/src/theme.js | 4 ++-- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/frontend/src/components/Header.jsx b/frontend/src/components/Header.jsx index d1a76071..af575c60 100644 --- a/frontend/src/components/Header.jsx +++ b/frontend/src/components/Header.jsx @@ -36,15 +36,22 @@ const Header = () => { }} > - - KoalaTech University - + + + KoalaTech University - CD Pipeline (Task 9.3C) + + + Date: Thu, 24 Sep 2026 20:23:00 +1000 Subject: [PATCH 3/3] Updating github action according to 10.2D --- .github/workflows/00-terraform.yml | 88 +++++++++++++++++++++++++++++ .github/workflows/01-ci.yml | 28 +++++++++ .github/workflows/05-monitoring.yml | 79 ++++++++++++++++++++++++++ frontend/Dockerfile | 3 +- kubernetes/monitoring/values.yaml | 31 ++++++++++ 5 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/00-terraform.yml create mode 100644 .github/workflows/05-monitoring.yml create mode 100644 kubernetes/monitoring/values.yaml diff --git a/.github/workflows/00-terraform.yml b/.github/workflows/00-terraform.yml new file mode 100644 index 00000000..1c1fdc80 --- /dev/null +++ b/.github/workflows/00-terraform.yml @@ -0,0 +1,88 @@ +name: 00 - Terraform Infrastructure + +on: + workflow_dispatch: + inputs: + action: + description: "Terraform action to execute" + required: true + default: "plan" + type: choice + options: + - plan + - apply + - destroy + + pull_request: + paths: + - "terraform/**" + + push: + branches: + - main + paths: + - "terraform/**" + +jobs: + terraform: + name: Terraform ${{ github.event.inputs.action || 'Validate & Plan' }} + runs-on: ubuntu-latest + + defaults: + run: + working-directory: ./terraform + + env: + ARM_CLIENT_ID: ${{ fromJSON(secrets.AZURE_CREDENTIALS).clientId }} + ARM_CLIENT_SECRET: ${{ fromJSON(secrets.AZURE_CREDENTIALS).clientSecret }} + ARM_SUBSCRIPTION_ID: ${{ fromJSON(secrets.AZURE_CREDENTIALS).subscriptionId }} + ARM_TENANT_ID: ${{ fromJSON(secrets.AZURE_CREDENTIALS).tenantId }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: 1.7.5 + + - name: Login to Azure CLI + uses: azure/login@v2 + with: + creds: ${{ secrets.AZURE_CREDENTIALS }} + + - name: Ensure Remote State Backend Exists + run: | + echo "Ensuring remote state backend resource group and storage account exist..." + az group create --name tfstate-rg --location "Australia East" || true + az storage account create \ + --name tfstate20245 \ + --resource-group tfstate-rg \ + --sku Standard_LRS \ + --encryption-services blob || true + az storage container create \ + --name tfstate \ + --account-name tfstate20245 \ + --auth-mode login || true + + - name: Terraform Format Check + run: terraform fmt -check + + - name: Terraform Init + run: terraform init + + - name: Terraform Validate + run: terraform validate + + - name: Terraform Plan + id: plan + run: terraform plan -no-color -out=tfplan + + - name: Terraform Apply + if: github.event_name == 'workflow_dispatch' && github.event.inputs.action == 'apply' + run: terraform apply -auto-approve tfplan + + - name: Terraform Destroy + if: github.event_name == 'workflow_dispatch' && github.event.inputs.action == 'destroy' + run: terraform destroy -auto-approve diff --git a/.github/workflows/01-ci.yml b/.github/workflows/01-ci.yml index 3a99a894..e8a14075 100644 --- a/.github/workflows/01-ci.yml +++ b/.github/workflows/01-ci.yml @@ -165,6 +165,34 @@ jobs: -t ${{ vars.ACR_LOGIN_SERVER }}/${{ matrix.image }}:${{ github.sha }} \ ./${{ matrix.service }} + # ========================================================= + # Docker Scout Security Scanning (DevSecOps) + # ========================================================= + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USER }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Docker Scout Quickview + uses: docker/scout-action@v1 + with: + command: quickview + image: ${{ vars.ACR_LOGIN_SERVER }}/${{ matrix.image }}:${{ github.sha }} + + - name: Docker Scout CVE Analysis + uses: docker/scout-action@v1 + with: + command: cves + image: ${{ vars.ACR_LOGIN_SERVER }}/${{ matrix.image }}:${{ github.sha }} + only-severities: critical,high + + - name: Docker Scout Recommendations + uses: docker/scout-action@v1 + with: + command: recommendations + image: ${{ vars.ACR_LOGIN_SERVER }}/${{ matrix.image }}:${{ github.sha }} + - name: Push Docker image with commit SHA run: | docker push \ diff --git a/.github/workflows/05-monitoring.yml b/.github/workflows/05-monitoring.yml new file mode 100644 index 00000000..d095e29a --- /dev/null +++ b/.github/workflows/05-monitoring.yml @@ -0,0 +1,79 @@ +name: 05 - Deploy Monitoring (Prometheus & Grafana) + +on: + workflow_run: + workflows: + - "04 - Deploy to Production" + types: + - completed + branches: + - main + + workflow_dispatch: + +jobs: + deploy-monitoring: + name: Deploy & Validate Prometheus and Grafana on AKS + runs-on: ubuntu-latest + + if: > + github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' + + 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: Set up Helm + uses: azure/setup-helm@v4 + + - name: Deploy kube-prometheus-stack via Helm + run: | + helm repo add prometheus-community https://prometheus-community.github.io/helm-charts + helm repo update + helm upgrade --install kube-prometheus-stack prometheus-community/kube-prometheus-stack \ + --namespace monitoring \ + --create-namespace \ + --values kubernetes/monitoring/values.yaml \ + --wait \ + --timeout 10m + + - name: Validate Monitoring Pods and Services + run: | + echo "==========================================" + echo "Monitoring Namespace Pods:" + kubectl get pods -n monitoring + echo "==========================================" + echo "Monitoring Namespace Services:" + kubectl get svc -n monitoring + echo "==========================================" + + - name: Output Grafana Dashboard Access URL + run: | + echo "Waiting for Grafana LoadBalancer External IP..." + for i in {1..30}; do + GRAFANA_IP=$(kubectl get svc kube-prometheus-stack-grafana -n monitoring -o jsonpath='{.status.loadBalancer.ingress[0].ip}') + if [ -n "$GRAFANA_IP" ]; then + echo "====================================================" + echo "Grafana Dashboard is accessible at:" + echo "URL: http://${GRAFANA_IP}" + echo "Username: admin" + echo "Password: AdminPassword123!" + echo "====================================================" + exit 0 + fi + echo "Waiting for external IP assignment ($i/30)..." + sleep 10 + done + echo "Note: If external IP is still pending, use 'kubectl port-forward svc/kube-prometheus-stack-grafana 3000:80 -n monitoring'" diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 6a1bcec6..8d993cf7 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -22,7 +22,8 @@ ENV VITE_ENROLLMENT_SERVICE_URL=${VITE_ENROLLMENT_SERVICE_URL} RUN npm run build -FROM nginx:1.27-alpine +#FROM nginx:1.27-alpine +FROM nginx:1-alpine-slim COPY nginx.conf /etc/nginx/conf.d/default.conf diff --git a/kubernetes/monitoring/values.yaml b/kubernetes/monitoring/values.yaml new file mode 100644 index 00000000..2901aacc --- /dev/null +++ b/kubernetes/monitoring/values.yaml @@ -0,0 +1,31 @@ +# Helm values configuration for kube-prometheus-stack on AKS (Task 10.2D) + +grafana: + enabled: true + adminPassword: "AdminPassword123!" + service: + type: LoadBalancer + defaultDashboardsEnabled: true + +prometheus: + enabled: true + prometheusSpec: + serviceMonitorSelectorNilUsesHelmValues: false + podMonitorSelectorNilUsesHelmValues: false + retention: 2d + resources: + requests: + cpu: 100m + memory: 256Mi + limits: + cpu: 500m + memory: 1024Mi + +nodeExporter: + enabled: true + +kubeStateMetrics: + enabled: true + +alertmanager: + enabled: false