diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 00000000..c24bb85f Binary files /dev/null and b/.DS_Store differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..ce158fbf --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.terraform/ +*terraform.tfstate +aks-terraform/secrets.tfvars \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..53a970ba --- /dev/null +++ b/Dockerfile @@ -0,0 +1,27 @@ +# TODO: Step 1 - Use an official Python runtime as a parent image. You can use `python:3.8-slim`. +FROM python:3.8-slim +# TODO: Step 2 - Set the working directory in the container +WORKDIR /app +# TODO: Step 3 Copy the application files in the container +COPY . . +# Install system dependencies and ODBC driver +RUN apt-get update && apt-get install -y \ + unixodbc unixodbc-dev odbcinst odbcinst1debian2 libpq-dev gcc && \ + apt-get install -y gnupg && \ + apt-get install -y wget && \ + wget -qO- https://packages.microsoft.com/keys/microsoft.asc | apt-key add - && \ + wget -qO- https://packages.microsoft.com/config/debian/10/prod.list > /etc/apt/sources.list.d/mssql-release.list && \ + apt-get update && \ + ACCEPT_EULA=Y apt-get install -y msodbcsql18 && \ + apt-get purge -y --auto-remove wget && \ + apt-get clean + +# Install pip and setuptools +RUN pip install --upgrade pip setuptools + +# TODO: Step 4 - Install Python packages specified in requirements.txt +RUN pip install --no-cache-dir -r requirements.txt +# TODO: Step 5 - Expose port +EXPOSE 5000 +# TODO: Step 6 - Define Startup Command +CMD ["python", "app.py"] \ No newline at end of file diff --git a/README.md b/README.md index 08407749..74cef606 100644 --- a/README.md +++ b/README.md @@ -13,15 +13,15 @@ Welcome to the Web App DevOps Project repo! This application allows you to effic ## Features - **Order List:** View a comprehensive list of orders including details like date UUID, user ID, card number, store code, product code, product quantity, order date, and shipping date. - + ![Screenshot 2023-08-31 at 15 48 48](https://github.com/maya-a-iuga/Web-App-DevOps-Project/assets/104773240/3a3bae88-9224-4755-bf62-567beb7bf692) - **Pagination:** Easily navigate through multiple pages of orders using the built-in pagination feature. - + ![Screenshot 2023-08-31 at 15 49 08](https://github.com/maya-a-iuga/Web-App-DevOps-Project/assets/104773240/d92a045d-b568-4695-b2b9-986874b4ed5a) - **Add New Order:** Fill out a user-friendly form to add new orders to the system with necessary information. - + ![Screenshot 2023-08-31 at 15 49 26](https://github.com/maya-a-iuga/Web-App-DevOps-Project/assets/104773240/83236d79-6212-4fc3-afa3-3cee88354b1a) - **Data Validation:** Ensure data accuracy and completeness with required fields, date restrictions, and card number validation. @@ -53,10 +53,250 @@ To run the application, you simply need to run the `app.py` script in this repos - **Database:** The application employs an Azure SQL Database as its database system to store order-related data. -## Contributors +## Contributors -- [Maya Iuga]([https://github.com/yourusername](https://github.com/maya-a-iuga)) +- [Maya Iuga](<[https://github.com/yourusername](https://github.com/maya-a-iuga)>) ## License This project is licensed under the MIT License. For more details, refer to the [LICENSE](LICENSE) file. + +## Robin Winters Azure End-to-End DevOps Pipeline Project + +1. _delivery-date column:_ delivery_date added to both backend (app.py) and frontend (order.html) files. +2. _removed delivery-date column:_ delivery_date removed from both backend (app.py) and frontend (order.html) files. + +### Containerization + +1. Dockerfile created based on python:3.8-slim +2. Docker image builded. + **imagename:** web-app-image + **imagetag:** 1.0 +3. Image pushed to Docker Hub + +- Image can be seen from the link below +- https://hub.docker.com/repository/docker/robinwinters/web-app-image/general + +### Networking Services Deployment with Terraform + +#### Overview + +This repository contains Terraform configurations for deploying networking resources in Azure, including a Virtual Network (VNet), subnets, and Network Security Groups (NSGs). This README provides an overview of the networking services deployed using Infrastructure as Code (IaC) with Terraform. + +#### Prerequisites + +Before running the Terraform configurations, ensure you have the following prerequisites set up: + +Azure subscription +Azure CLI installed and configured +Terraform CLI installed + +#### Terraform Configuration + +1. Input Variables + + - resource_group_name: The name of the Azure resource group where networking resources will be created. + - location: The Azure region where networking resources will be deployed. + - vnet_address_space: The address space for the Virtual Network (VNet). + +2. Networking Resources + + - Azure Resource Group + - Virtual Network (VNet) + - Subnets + - Network Security Group (NSG) + +3. Output Variables + - vnet_id: ID of the Virtual Network (VNet). + - control_plane_subnet_id: ID of the control plane subnet. + - worker_node_subnet_id: ID of the worker node subnet. + - resource_group_name: Name of the Azure Resource Group for networking resources. + - aks_nsg_id: ID of the Network Security Group (NSG) for AKS. + +#### Usage + +1. Clone Repository: Clone this repository to your local machine. +2. Set Terraform Variables: Update the variables.tf file with your desired values for input variables. +3. Initialize Terraform: Run terraform init to initialize the Terraform configuration. +4. Review Execution Plan: Run terraform plan to review the execution plan and ensure it matches your expectations. +5. Apply Terraform Changes: Run terraform apply to apply the Terraform changes and deploy networking resources to Azure. + +- \*\*git can only support files of a certain size & that the .terraform is larger so has to be avoided + +### AKS Cluster steps with Terraform + +1. Clone the repository + +``` +git clone https://github.com/robinucar/Web-App-DevOps-Project.git +cd Web-App-Devops-Project +cd aks-terraform +cd aks-cluster-module + +``` + +2. Define Input Variables: + + - aks_cluster_name: The name of the AKS cluster. + - cluster_location: The Azure region where the AKS cluster will be created. + - dns_prefix: DNS prefix for the AKS cluster. + - kubernetes_version: The version of Kubernetes to be used. + - service_principal_client_id: Client ID of the service principal used for authentication. + - service_principal_client_secret: Client Secret associated with the service principal. + - resource_group_name: Name of the Azure Resource Group for networking resources. + - vnet_id: ID of the Virtual Network (VNet). + - control_plane_subnet_id: ID of the control plane subnet. + - worker_node_subnet_id: ID of the worker node subnet. + +3. Define AKS Cluster Configuration: + + - Update the main.tf file to define the AKS cluster configuration using the azurerm_kubernetes_cluster resource block. Specify the desired configuration options such as name, location, DNS prefix, Kubernetes version, and service principal details. + +4. Define Output Variables: + Update the output.tf file to define output variables for the AKS cluster. The output variables include: + + - aks_cluster_name: Name of the AKS cluster. + - aks_cluster_id: ID of the AKS cluster. + - aks_kubeconfig: Kubeconfig file for accessing the AKS cluster. + +5. Initialize Terraform: + + - Run the following command to initialize Terraform and download the required provider plugins: + + ``` + terraform init + + ``` + +### WEB-APP-DEVOPS-PROJECT Terraform Azure Aks Cluster Deployment + +This project automates the deployment of an Azure Kubernetes Service (AKS) cluster using Terraform. It sets up networking resources and provisions an AKS cluster on Microsoft Azure. + +#### Prerequisites + +Before you begin, make sure you have the following prerequisites installed: + +- [Terraform](https://www.terraform.io/downloads.html) +- [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) + +You also need an Azure subscription and Service Principal credentials to authenticate with Azure. + +## Project Structure + +The project structure is organized as follows: + +- `main.tf`: The main Terraform configuration file that orchestrates the deployment of resources. +- `variables.tf`: Defines input variables used in the main configuration file. +- `networking-module/`: Directory containing the Terraform module for networking resources. +- `aks-cluster-module/`: Directory containing the Terraform module for AKS cluster provisioning. +- `output.tf`: Defines output variables to retrieve information after deployment. + +## Configuration + +### Provider Setup + +The `main.tf` file configures the `azurerm` provider to interact with Azure APIs. Replace `var.client_id` and `var.client_secret` with your Azure Service Principal credentials. + +```hcl +provider "azurerm" { + features {} + + client_id = var.client_id + client_secret = var.client_secret + subscription_id = "" + tenant_id = "" +} + + +``` + +### WEB-APP-DEVOPS-PROJECT Kubernetes Deployment + +This repository contains detailed documentation for the deployment of a containerized web application onto an Azure Kubernetes Service (AKS) cluster using Kubernetes manifests. Below, we outline the key stages of the deployment process and provide insights into the configuration settings, deployment strategy, testing, validation, and distribution plan for the application. + +#### Deployment and Service Manifests + +1. Deployment Manifest: + - The Deployment manifest (application-manifest.yaml) defines a Kubernetes Deployment named flask-app-deployment for the containerized web application. + - Key concepts and configuration settings include: + - Specifying two replicas for scalability and high availability. + - Using labels (e.g., app: flask-app) to match pods and ensure efficient traffic routing. + - Configuring the container image (robinwinters/web-app-image:1.0) and exposing port 5000 for communication. + - Implementing a RollingUpdate deployment strategy to ensure seamless application updates. +2. Service Manifest: + - The Service manifest within the same file defines a Kubernetes Service named flask-app-service to facilitate internal communication within the AKS cluster. + - Key configuration settings include: + - Matching the selector with the labels of the Deployment pods. + - Using the ClusterIP type and TCP protocol on port 80 to communicate with the pods. + - Setting the targetPort to 5000 to access the application's user interface. + +#### Deployment Strategy + +The chosen deployment strategy is RollingUpdate. This strategy was selected for its ability to update the application with zero downtime by gradually replacing old pods with new ones. It aligns with the application's requirements for continuous availability and reliability, ensuring seamless updates without impacting user experience. + +### CI/CD Pipeline Documentation + +This section provides comprehensive information about the CI/CD pipeline configured in Azure DevOps for this project. + +#### Configuration and Settings + +- Source Repository: + The source code for this project is hosted on GitHub. The repository contains the application code along with the necessary configuration files for the CI/CD pipeline. + +- Build Pipeline (Starter Pipeline): + The CI/CD pipeline is configured using the Starter Pipeline template provided by Azure DevOps. This simple pipeline is a foundation for further customization and includes basic build and test stages. It automatically triggers on each push to the main branch of the GitHub repository. + +- Integration with Docker Hub: + The CI/CD pipeline is integrated with Docker Hub for container image management. A Docker service connection is configured in Azure DevOps to facilitate seamless integration with Docker Hub's container registry. To integrate the CI/CD pipeline with Docker Hub, follow these steps: + + 1. Create Docker Hub Personal Access Token: + + - Generate a Personal Access Token (PAT) in Docker Hub with the necessary permissions to push Docker images. + + 2. Create Docker Service Connection in Azure DevOps + + - Navigate to Project Settings > Service connections in Azure DevOps. + - Click on "New service connection" and choose "Docker Registry". + - Enter your Docker Hub credentials and the Personal Access Token created earlier. + + 3. Update Pipeline YAML: + - Add tasks to your pipeline YAML file to build and push Docker images to Docker Hub as part of the build process. + +- Integration with AKS: + While the Starter Pipeline does not include specific deployment steps to AKS, it can be extended to incorporate deployment tasks using Azure Kubernetes Service (AKS) in subsequent iterations. To integrate the CI/CD pipeline with Azure Kubernetes Service (AKS), follow these steps: + + 1. Create AKS Service Connection in Azure DevOps + + - Navigate to Project Settings > Service connections in Azure DevOps. + - Click on "New service connection" and choose "Kubernetes". + - Provide the necessary details such as AKS cluster name, resource group, and authentication method. + + 2. Update Pipeline YAML: + + - Add tasks to your pipeline YAML file to deploy the application to AKS using kubectl commands or Azure DevOps deployment tasks. + +- Validation Steps: + + Since the Starter Pipeline focuses on basic build and test stages, the validation steps primarily involve testing the functionality of the application locally and ensuring that the build process completes successfully. + +- Testing Functionality: + + - Local Testing: Before pushing changes to the main branch, local testing is performed to ensure that the application functions as expected. + + - Build Verification: Upon triggering the CI/CD pipeline, the build process verifies that the application code compiles successfully and any automated tests included in the pipeline pass. + +### Monitoring Strategy for AKS Cluster + +This section outlines the comprehensive monitoring strategy implemented for the Azure Kubernetes Service (AKS) cluster used in this project. + +#### Metrics Explorer Charts + +1. Average Node CPU and Memory Usage + +- Tracks CPU and memory usage to optimize resource allocation. + ![Average Node CPU Usage Percentage](/assests/Avg_CPU.png) + +2. Pods Counts with Phase + +- Monitors pod lifecycle management and workload distribution. + ![Average Number of Pods by Phase](assests/number_of_pods.png) diff --git a/aks-terraform/.DS_Store b/aks-terraform/.DS_Store new file mode 100644 index 00000000..696c0e84 Binary files /dev/null and b/aks-terraform/.DS_Store differ diff --git a/aks-terraform/.terraform.lock.hcl b/aks-terraform/.terraform.lock.hcl new file mode 100644 index 00000000..2ef938a0 --- /dev/null +++ b/aks-terraform/.terraform.lock.hcl @@ -0,0 +1,21 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/azurerm" { + version = "3.0.0" + constraints = "3.0.0" + hashes = [ + "h1:zABIsWI7y89dI3MIHPjLwjzp2VN87f3fOzEypuGI/OA=", + "zh:23a039a606cc194594f7c15cd8deef15c5183e11a40e96adee2f7317dbfa18aa", + "zh:414890618efc6caccf60b81fcce18a7e69a6d81599678d24f538d53726f49c57", + "zh:7c9a5d3c416766c6f624e186ee2f5b216dd5a9ffef40bfea42ceccf2b217e0d3", + "zh:82bbeaa6e10d0834d05c2ea55182ce6e147299b1257b445327ff6ff9dfdff3e7", + "zh:96d5f7737a3d10cc25815f1a220ef8ffe3641ee3229c7738804dc8cff71663fa", + "zh:ac359915e11a4fa234476cca5e701631ba563d8192dd3f1d31b51674411a0394", + "zh:bdf07291bb4f41ba304f12b298a066ac70925b3749c01aa90276727cfb0b2662", + "zh:cf7b4f9c313155b7d5c98e0cbbcfec40c789fccf431875b4db630e9e58f3ae6c", + "zh:d1fd0d3a1017427ab6f4fadb3310b4b488ab020a541778653c03c51e5e1df809", + "zh:db946fc8cfc15abe18314dc3dbcbb630243dc34c29f81a728e1397d797dca6a0", + "zh:e07f73c2745b56043d8b779f2987eb1a5f812645db6ac8fa7878ad23f6a79459", + ] +} diff --git a/aks-terraform/aks-cluster-module/.terraform.lock.hcl b/aks-terraform/aks-cluster-module/.terraform.lock.hcl new file mode 100644 index 00000000..49223d74 --- /dev/null +++ b/aks-terraform/aks-cluster-module/.terraform.lock.hcl @@ -0,0 +1,21 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/azurerm" { + version = "3.90.0" + hashes = [ + "h1:8exKO4IgZ9qLImDCAig+cKgONFUV/gW6pIlXc9CnPVk=", + "zh:194a4342620958403beabf4d57d552133ca6ac18eef3027d6d1a98846b52f8ab", + "zh:1d8ee378aaa793e3288c9328e056763c98d0f2e8560357296bc3446fbd3b1b9d", + "zh:24aba7903e912570e36edb03f79c68028d3e254175947b588c96521f09f89df4", + "zh:27f91fbeef9d04c6382014b6c32883a96dbe91cf7a4fa07a97be5d6b03991f95", + "zh:59eeaa2f50f698bab6f36ada0e865d6b624625ff5d76309334b3c3aa366cb692", + "zh:732af42d18fa222ee88f7f97c0898d4955ae48fde5456e22af3b8f5d324c6b41", + "zh:766034eac5e6a66cf3631580956dd584b1c2e6134167302fc8b95d6b42ebf08b", + "zh:a5b2ec52abfc3fb154047af45ea692c98c646c2b5c336b12b6341a49be95025c", + "zh:bdd72f85d770fa4a2e6ebf542858341d3df7e858a4d70c0f94df758721bcd811", + "zh:e9f15f2399c667c24b3daf8a843f1cadd13bc619becf6362b46c3216b17009b1", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + "zh:f73b1ec8b372bc1480ca0d93e78914f1c9cebe81395f20273d7bc99579b84809", + ] +} diff --git a/aks-terraform/aks-cluster-module/main.tf b/aks-terraform/aks-cluster-module/main.tf new file mode 100644 index 00000000..0fd5a23d --- /dev/null +++ b/aks-terraform/aks-cluster-module/main.tf @@ -0,0 +1,22 @@ +resource "azurerm_kubernetes_cluster" "aks_cluster" { + name = var.aks_cluster_name + location = var.cluster_location + resource_group_name = var.resource_group_name + dns_prefix = var.dns_prefix + kubernetes_version = var.kubernetes_version + + + default_node_pool { + name = "default" + node_count = 1 + vm_size = "Standard_DS2_v2" + enable_auto_scaling = true + min_count = 1 + max_count = 3 + } + + service_principal { + client_id = var.service_principal_client_id + client_secret = var.service_principal_client_secret + } +} diff --git a/aks-terraform/aks-cluster-module/output.tf b/aks-terraform/aks-cluster-module/output.tf new file mode 100644 index 00000000..e4e9e97f --- /dev/null +++ b/aks-terraform/aks-cluster-module/output.tf @@ -0,0 +1,14 @@ +output "aks_cluster_name" { + description = "Name of the AKS cluster." + value = azurerm_kubernetes_cluster.aks_cluster.name +} + +output "aks_cluster_id" { + description = "ID of the AKS cluster." + value = azurerm_kubernetes_cluster.aks_cluster.id +} + +output "aks_kubeconfig" { + description = "Kubeconfig file for accessing the AKS cluster." + value = azurerm_kubernetes_cluster.aks_cluster.kube_config_raw +} diff --git a/aks-terraform/aks-cluster-module/variables.tf b/aks-terraform/aks-cluster-module/variables.tf new file mode 100644 index 00000000..aeea5024 --- /dev/null +++ b/aks-terraform/aks-cluster-module/variables.tf @@ -0,0 +1,64 @@ +# aks-cluster/variables.tf + +variable "aks_cluster_name" { + description = "The name of the AKS cluster that will be created" + type = string +} + +variable "cluster_location" { + description = "The Azure region where the AKS cluster will be created" + type = string + default = "UK South" +} + +variable "dns_prefix" { + description = "DNS prefix for the AKS cluster, which is used to create a unique DNS name for the cluster." + type = string +} + +variable "kubernetes_version" { + description = "The version of Kubernetes to be used for the AKS cluster." + type = string +} + +variable "service_principal_client_id" { + description = "The lient ID of the service principal used for authenticating and managing the AKS cluster." + type = string +} + +variable "service_principal_client_secret" { + description = "The Client Secret associated with the service principal used for AKS cluster authentication." + type = string +} + +# Input variables from the networking module +variable "resource_group_name" { + description = "Name of the Azure Resource Group for networking resources." + type = string + +} + +variable "vnet_id" { + description = "ID of the Virtual Network (VNet)." + type = string + +} + + +variable "control_plane_subnet_id" { + description = "ID of the control plane subnet." + type = string + +} + +variable "worker_node_subnet_id" { + description = "ID of the worker node subnet." + type = string + +} + +variable "aks_nsg_id" { + description = "ID of the Network Security Group (NSG) for AKS." + type = string + +} diff --git a/aks-terraform/kubeconfig b/aks-terraform/kubeconfig new file mode 100644 index 00000000..37e62955 --- /dev/null +++ b/aks-terraform/kubeconfig @@ -0,0 +1,7 @@ +╷ +│ Warning: No outputs found +│  +│ The state file either has no outputs defined, or all the defined outputs are empty. Please define an output in your configuration with the +│ `output` keyword and run `terraform refresh` for it to become available. If you are using interpolation, please verify the interpolated value +│ is not empty. You can use the `terraform console` command to assist. +╵ diff --git a/aks-terraform/main.tf b/aks-terraform/main.tf new file mode 100644 index 00000000..1747ca1a --- /dev/null +++ b/aks-terraform/main.tf @@ -0,0 +1,47 @@ +terraform { + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "=3.0.0" + } + } +} + +provider "azurerm" { + features {} + client_id = var.client_id + client_secret = var.client_secret + subscription_id = "88de54f1-2c72-4d16-9eb0-beb8d6da1d5a" + tenant_id = "47d4542c-f112-47f4-92c7-a838d8a5e8ef" +} + +module "networking" { + source = "./networking-module" + + # Input variables for the networking module + resource_group_name = "web-app-networking-resource-group" + location = "UK South" + vnet_address_space = ["10.0.0.0/16"] + + # Define more input variables as needed... +} + +module "aks_cluster" { + source = "./aks-cluster-module" + + # Input variables for the AKS cluster module + aks_cluster_name = "terraform-aks-cluster" + cluster_location = "UK South" + dns_prefix = "myaks-project" + kubernetes_version = "1.26.6" # Adjust the version as needed + service_principal_client_id = var.client_id + service_principal_client_secret = var.client_secret + # Input variables referencing outputs from the networking module + resource_group_name = module.networking.resource_group_name + vnet_id = module.networking.vnet_id + control_plane_subnet_id = module.networking.control_plane_subnet_id + worker_node_subnet_id = module.networking.worker_node_subnet_id + aks_nsg_id = module.networking.aks_nsg_id + + # Define more input variables as needed... +} diff --git a/aks-terraform/networking-module/.terraform.lock.hcl b/aks-terraform/networking-module/.terraform.lock.hcl new file mode 100644 index 00000000..49223d74 --- /dev/null +++ b/aks-terraform/networking-module/.terraform.lock.hcl @@ -0,0 +1,21 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/azurerm" { + version = "3.90.0" + hashes = [ + "h1:8exKO4IgZ9qLImDCAig+cKgONFUV/gW6pIlXc9CnPVk=", + "zh:194a4342620958403beabf4d57d552133ca6ac18eef3027d6d1a98846b52f8ab", + "zh:1d8ee378aaa793e3288c9328e056763c98d0f2e8560357296bc3446fbd3b1b9d", + "zh:24aba7903e912570e36edb03f79c68028d3e254175947b588c96521f09f89df4", + "zh:27f91fbeef9d04c6382014b6c32883a96dbe91cf7a4fa07a97be5d6b03991f95", + "zh:59eeaa2f50f698bab6f36ada0e865d6b624625ff5d76309334b3c3aa366cb692", + "zh:732af42d18fa222ee88f7f97c0898d4955ae48fde5456e22af3b8f5d324c6b41", + "zh:766034eac5e6a66cf3631580956dd584b1c2e6134167302fc8b95d6b42ebf08b", + "zh:a5b2ec52abfc3fb154047af45ea692c98c646c2b5c336b12b6341a49be95025c", + "zh:bdd72f85d770fa4a2e6ebf542858341d3df7e858a4d70c0f94df758721bcd811", + "zh:e9f15f2399c667c24b3daf8a843f1cadd13bc619becf6362b46c3216b17009b1", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + "zh:f73b1ec8b372bc1480ca0d93e78914f1c9cebe81395f20273d7bc99579b84809", + ] +} diff --git a/aks-terraform/networking-module/main.tf b/aks-terraform/networking-module/main.tf new file mode 100644 index 00000000..cd8b42ba --- /dev/null +++ b/aks-terraform/networking-module/main.tf @@ -0,0 +1,65 @@ +# Create the Azure Resource Group for networking resources +resource "azurerm_resource_group" "networking" { + name = var.resource_group_name + location = var.location +} + +# Define the Virtual Network (VNet) for the AKS cluster +resource "azurerm_virtual_network" "aks_vnet" { + name = "aks-vnet" + address_space = var.vnet_address_space + location = azurerm_resource_group.networking.location + resource_group_name = azurerm_resource_group.networking.name +} + +# Define subnets within the VNet for control plane and worker nodes +resource "azurerm_subnet" "control_plane_subnet" { + name = "aks-subnet-control-plane" + resource_group_name = azurerm_resource_group.networking.name + virtual_network_name = azurerm_virtual_network.aks_vnet.name + address_prefixes = ["10.0.1.0/24"] +} + +resource "azurerm_subnet" "worker_node_subnet" { + name = "aks-node-subnet" + resource_group_name = azurerm_resource_group.networking.name + virtual_network_name = azurerm_virtual_network.aks_vnet.name + address_prefixes = ["10.0.2.0/24"] +} + +# Define Network Security Group (NSG) for the AKS subnet +resource "azurerm_network_security_group" "aks_nsg" { + name = "aks-nsg" + location = azurerm_resource_group.networking.location + resource_group_name = azurerm_resource_group.networking.name +} + +# Allow inbound traffic to kube-apiserver (TCP/443) from your public IP address +resource "azurerm_network_security_rule" "kube_apiserver" { + name = "aks-kubi-api-server" + priority = 1001 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_port_range = "*" + destination_port_range = "443" + source_address_prefix = "150.143.254.11" + destination_address_prefix = "*" + resource_group_name = azurerm_resource_group.networking.name + network_security_group_name = azurerm_network_security_group.aks_nsg.name +} + +# Allow inbound traffic for SSH (TCP/22) - Optional +resource "azurerm_network_security_rule" "ssh" { + name = "aks-ssh" + priority = 1002 + direction = "Inbound" + access = "Allow" + protocol = "Tcp" + source_port_range = "*" + destination_port_range = "22" + source_address_prefix = "150.143.254.11" + destination_address_prefix = "*" + resource_group_name = azurerm_resource_group.networking.name + network_security_group_name = azurerm_network_security_group.aks_nsg.name +} diff --git a/aks-terraform/networking-module/output.tf b/aks-terraform/networking-module/output.tf new file mode 100644 index 00000000..14d2da4b --- /dev/null +++ b/aks-terraform/networking-module/output.tf @@ -0,0 +1,25 @@ +output "vnet_id" { + description = "ID of the Virtual Network (VNet)." + value = azurerm_virtual_network.aks_vnet.id +} + +output "control_plane_subnet_id" { + description = "ID of the control plane subnet." + value = azurerm_subnet.control_plane_subnet.id +} + +output "worker_node_subnet_id" { + description = "ID of the worker node subnet." + value = azurerm_subnet.worker_node_subnet.id +} + +output "resource_group_name" { + description = "Name of the Azure Resource Group for networking resources." + value = azurerm_resource_group.networking.name +} + +# Define more output variables as needed... +output "aks_nsg_id" { + description = "ID of the Network Security Group (NSG) for AKS." + value = azurerm_network_security_group.aks_nsg.id +} diff --git a/aks-terraform/networking-module/variables.tf b/aks-terraform/networking-module/variables.tf new file mode 100644 index 00000000..d26938e1 --- /dev/null +++ b/aks-terraform/networking-module/variables.tf @@ -0,0 +1,18 @@ +variable "resource_group_name" { + description = "The Azure resource group where the networking resources will be created in" + type = string + default = "azure-devops-project-resource-group" + +} + +variable "location" { + description = "The Azure region where the networking resources will be deployed." + type = string + default = "UK South" +} + +variable "vnet_address_space" { + description = "Address space for the Virtual Network (VNet)." + type = list(string) + default = ["10.0.0.0/16"] +} diff --git a/aks-terraform/variables.tf b/aks-terraform/variables.tf new file mode 100644 index 00000000..a18918db --- /dev/null +++ b/aks-terraform/variables.tf @@ -0,0 +1,13 @@ +variable "client_id" { + description = "Access key for the provider" + type = string + sensitive = true + +} + +variable "client_secret" { + description = "Secret key for the provider" + type = string + sensitive = true + +} diff --git a/application-manifest.yaml b/application-manifest.yaml new file mode 100644 index 00000000..3447d420 --- /dev/null +++ b/application-manifest.yaml @@ -0,0 +1,40 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: flask-app-deployment +spec: + replicas: 2 + selector: + matchLabels: + app: flask-app + template: + metadata: + labels: + app: flask-app + spec: + containers: + - name: flask-app-container + image: robinwinters/web-app-image:1.0 + ports: + - containerPort: 5000 + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 + maxUnavailable: 1 + +--- + +apiVersion: v1 +kind: Service +metadata: + name: flask-app-service +spec: + selector: + app: flask-app + ports: + - protocol: TCP + port: 80 + targetPort: 5000 + type: ClusterIP + diff --git a/assests/Avg_CPU.png b/assests/Avg_CPU.png new file mode 100644 index 00000000..d089a7cf Binary files /dev/null and b/assests/Avg_CPU.png differ diff --git a/assests/Avg_Used_Disk_Percentage.png b/assests/Avg_Used_Disk_Percentage.png new file mode 100644 index 00000000..17a716a2 Binary files /dev/null and b/assests/Avg_Used_Disk_Percentage.png differ diff --git a/assests/Sum_Total_bytes_read .png b/assests/Sum_Total_bytes_read .png new file mode 100644 index 00000000..5d7b6ade Binary files /dev/null and b/assests/Sum_Total_bytes_read .png differ diff --git a/assests/Sum_Total_bytes_written.png b/assests/Sum_Total_bytes_written.png new file mode 100644 index 00000000..57958cee Binary files /dev/null and b/assests/Sum_Total_bytes_written.png differ diff --git a/assests/number_of_pods.png b/assests/number_of_pods.png new file mode 100644 index 00000000..f5244059 Binary files /dev/null and b/assests/number_of_pods.png differ diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 00000000..e1b8e708 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,24 @@ +# Starter pipeline + +trigger: +- main + +pool: + vmImage: ubuntu-latest + +steps: +- task: Docker@2 + inputs: + containerRegistry: 'DockerHub-AuthToken' + repository: 'robinwinters/web-app-image' + command: 'buildAndPush' + Dockerfile: '**/Dockerfile' + tags: | + latest + +- task: KubernetesManifest@1 + inputs: + action: 'deploy' + connectionType: 'kubernetesServiceConnection' + kubernetesServiceConnection: 'DevOps-AKS-Integration' + manifests: 'application-manifest.yaml' diff --git a/kubeconfig b/kubeconfig new file mode 100644 index 00000000..692a8879 --- /dev/null +++ b/kubeconfig @@ -0,0 +1,20 @@ +apiVersion: v1 +clusters: +- cluster: + certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUU2RENDQXRDZ0F3SUJBZ0lRYkRjKzBkM0tsYmVuQ2hDdWNmMFNVVEFOQmdrcWhraUc5dzBCQVFzRkFEQU4KTVFzd0NRWURWUVFERXdKallUQWdGdzB5TkRBeU1Ea3hORFE0TWpSYUdBOHlNRFUwTURJd09URTBOVGd5TkZvdwpEVEVMTUFrR0ExVUVBeE1DWTJFd2dnSWlNQTBHQ1NxR1NJYjNEUUVCQVFVQUE0SUNEd0F3Z2dJS0FvSUNBUURMCi8wYncrQ3pSQktPd1hNdU9veklYdFRrSG5wVjhTc3hWWDk5T3ZUYkkvQzE4dDdwOURxWTA0ZzRCVTV1b1pXWjAKTnVMOEkzWEM5ZlI0dmZ1dkNXK0pvNW1uTmE1ZzdEcUNMV05iQ1hyRzZOVnhId0JsTUpwWHFtOGNDOER3UUVTRQpiSjlrM3JOMzM3VXNtNm1KNTc4RU44QkJ4aFdkYXRQU0ZnWmVJT2hJZjk2RVoxMHdhMjNoQlorUDkzbGlCODBoCm0yUHVraGZsUlFQSzMwZmc3d0pGcFRsVzVHNVkwM0JXU0dZZlo2QzVlWWE0V1J1ZDdvTlZIa1JkeGMxK1FMWjgKajcrNWJia1ptZmtnaDliUlVxWElzVStKVW92a3BhaGNZWWVTZDQ4OTJqbEhoTGZkK3huT01lbi8yNXJNSWQzNQoza2FabGhibUZpaGRLMy9lRVhFaUg3eG5KR3BYUFpPa2d3TUJHeDhiT2t1d1g2Z2lhdHBWcmxnK1QxYXdiWE9ECm1ZU3B5WW55elZ4Mmp5Y3RtQytnNzVJR3hDY2RBU0NYUXdwZDRlakpVU2Z1WTlmOHF4T090VWI5bFV0VXJQYmMKTGVadUVONmM0WUozbHpHNlFYaXZvM1l6cDZrWUd3Y2RuYUQwWk1sUXlyRjcySzUyeHdXZGd3ZHd6L1ZEOHI2OAowcVJBY3htZ2Q5Z3JMcjU3c2h4cDFCT0ptdkpSei9lYkpwbUFqcGxPTG5NVkFqb2FLV2ZZazd0SmFqWW5NNTdDCjBYS1lWL1ljMU4zWTlVNjgyVXJYcU5pVk41MHdTM3oyczEyT1F1UVdnY21ESERaMHJLWXdIOGhhN2drTmd6SWEKUS9LSkUvblJjS1k4UkVSSkVRakpOQnp0Q2JQSjc4TlZUQWJEUGRUQ1hRSURBUUFCbzBJd1FEQU9CZ05WSFE4QgpBZjhFQkFNQ0FxUXdEd1lEVlIwVEFRSC9CQVV3QXdFQi96QWRCZ05WSFE0RUZnUVVBS1oyMEhiUW5YcENsK2RaCkVSaVRnUDV3SW1jd0RRWUpLb1pJaHZjTkFRRUxCUUFEZ2dJQkFHWGFoNTZYWWxwb2tlaGtTSE91YkE2c2h4dWMKSzVYb0lBUTNES003SzRUcTlrbFpxZVUxSHQ1eDkrcWxJdXVCNTJGblg4SmRpd2xQa01DcjAxdHQ2eS92NEhkRgpFcmgrdjNPTGlvcEM5V3QvVDNjVzEvOExwdEVFVGJ5dTI3empSd1kvVHczd2JucVo5WUhCU3E3REpzQlEzTktPCkliUDdHRG9QbjNhazQ0OFVPT1dvd3B1ZGE2azllbHF1YlV4MzNScXNhQ3lQNFRmS29BRkdJc1FycHV4WWlrL1UKc0l2VUJSMXVwMEQyVkp0WDJPOEgzc1k5TkdlWHJoRDB2MHpvZXV1T3ZBZjBTRU0wTzBHQUVYQlo1clhJdEV1MApoY2l2eXp4VkhpK2xhT2xBZmo5UUxKSGhQVDl5K0srNm42UWRnbkw4OWRQSmNCeEV5YmtiVGwxcDZFUHJvSE5VCjB4Z0VwVmY2NW9rejlpRElnall0YmNucEJIODAvOTBRSFZjbnBIMnZKQm93N1FOMm5SL2htbWcxYmh1Sm9VdWYKSG1HcEMyakE2TWg1T2ViVk9yVXB5eXMvTEpidFRsbTllaEozR0ZzUnVQVFh1TlgvZXRocUxMcVc1MENXcmg4bwp6UDFkRTN3RHlhVWw3OWtGYitaRmVveWxrb3k1TnZlTEV2a21DcTZUcDAveEt5WXFIazJrOUkrbktEb3JSbmxHClpDUXl1cm5aQVBLT2F1eE1PVm56OEd1MUkzeDVsSC8rSWdYSzJCcTZubWtNMXd3UjZLRTJ0WDN2U2hGWWJScDYKNTJ4UDMvRmVscGJvQlZ6SVZ3TFhOcHd4Smg0WTdEemFpNld4bHgrQjdYUU13ZDNKdzJGWFRJRm1jbTcvTTNGMwpSVjQzK1k5RzVtTkhzdUVkCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + server: https://myaks-project-jy4wy2ko.hcp.uksouth.azmk8s.io:443 + name: terraform-aks-cluster +contexts: +- context: + cluster: terraform-aks-cluster + user: clusterUser_web-app-networking-resource-group_terraform-aks-cluster + name: terraform-aks-cluster +current-context: terraform-aks-cluster +kind: Config +preferences: {} +users: +- name: clusterUser_web-app-networking-resource-group_terraform-aks-cluster + user: + client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZIakNDQXdhZ0F3SUJBZ0lSQU01Qi96R2RWNHZIanQva1U3UnJCa2d3RFFZSktvWklodmNOQVFFTEJRQXcKRFRFTE1Ba0dBMVVFQXhNQ1kyRXdIaGNOTWpRd01qQTVNVFEwT0RJMVdoY05Nall3TWpBNU1UUTFPREkxV2pBdwpNUmN3RlFZRFZRUUtFdzV6ZVhOMFpXMDZiV0Z6ZEdWeWN6RVZNQk1HQTFVRUF4TU1iV0Z6ZEdWeVkyeHBaVzUwCk1JSUNJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBZzhBTUlJQ0NnS0NBZ0VBeTJEeGFDUkdPZjFkOVdZdWxtM3QKWllZbWtrMCsyeG10UmtVdE51WFpjV1Jhc0RjWmt6STBiemNCTk9WS1RNZDFmMVlrU3l6S1JLekFObkRBRWxHMwpQVWgranhvaFFPUkF2d21NT2dQZnN6clE4VXc2dmhTNG9hYUw4Zm11QVVLWjFFMUlwSG5YQmU1eFBRcityMFJFCklrcWRRNnhlMnYreUZiby9LQ3NLYnlkelZrc21XcTFienNLS0NINWp3UHc5b3U0Q0hhWlVqN3lmaFBOSGI1dS8KTlpnUHFSckJobm8vNHV4YnJhRTkwZ2RtTEVML2ZGQjVuOGZLTFZsY21mVmxEcW9tTG8xTk5xL1o4ZGVmNmdpSApSZVhQK05qa0tYVTI5RThibXVuU2RBZ2llcFJvdjVzb1UvdjNkZGtMU2d6Y20yKzAxZ2xSRGk1LzdzMm4wOW0xCmkwSVVWd3VDb3dnaDl5UEtmMURIWW9wWCtTaEl6SDdNWVlTSktjMVRuMWwra3FrejAwakljTEwraGRDckRjWGcKcWIyR3NvT2pUdzQzT0xCYUNYTW5rYm1TdENwWVBGRDMwY05uTlNqSmlDVm1YdFVzdmswQmthNmhVaVdhWmdvNwpDNDd6WmZhSnMyTUo2bXNITTBlQWJxL0h5N09GOUIrRlB6Rk9wd0ZyL2xsRE1OSnZLaDRaSGhTUkVqaVorMHZ5CnJodnNINnpHMmR3bllPTHFHVXBSNVh6YURKS0s5alFhQTBmU0s3Q3c1aDFieGlKK0dldHAvdGg5NDl1dHE2VkEKTGI0VUtYNjdrS2FhSWFxdy9vNWNEd3JRWWNaTVJjUzFJUHpaZWFscGFrNmRLWFVmREFNelVrcGNXcEtiSTJ0bApZbEllQ3NQbnVGa3BUN1lWTFhwb1pITUNBd0VBQWFOV01GUXdEZ1lEVlIwUEFRSC9CQVFEQWdXZ01CTUdBMVVkCkpRUU1NQW9HQ0NzR0FRVUZCd01DTUF3R0ExVWRFd0VCL3dRQ01BQXdId1lEVlIwakJCZ3dGb0FVQUtaMjBIYlEKblhwQ2wrZFpFUmlUZ1A1d0ltY3dEUVlKS29aSWh2Y05BUUVMQlFBRGdnSUJBRnlBdm5DdmtxaW5zbUJ4NlJDRgpZdXh6cHNCT1E3b3hERjErQ25VRExRbmZCY2hSQTFLUWxKcWZrbnVXZGxLb3lzb21qNHRtK0xucGk4WkpBOXRKClA2cmxaNjJSUS82VGhoK3EraTBqQ2ZmeVRlY3gyTUJwNERCVEI2K00wcEh6eFRXR1FZb1dMbGt3azd5cEkvQkYKT2o1ZElGN1R6dWxoNHN2WWU5UjhoRjBtTWhaVEM4aHZIeEwxWFZmK3FSaEdXRFVpVDYxcnNpR2QwalN1N2I4TwpPd0RqZ2laVExyMWU4UGpvTk1zSENyeDNiMzJrbGE2TklYa2V0d3lETW5wV3ViV0RoNWg0YXcxTDdqa0ZMOE1UCjJ1dThWbFNxNk1NV0M4YlJta1dCOHU0WWtqbkFwdys3T3R0K3grMkpUdFZ1TEc4UVRlaGc1dEF6NnhUWDMyYysKMUtBNUFldzltdEhJRjQreVFTeXczbDhVVFIyWFNhM2x0MlkzOTB0TzVPZTFHY3BKMEJZTGZzdXJ2ZGtML3hDcQo0SWVoenFLSDRKTGtRT1F5Wjl5bFYvNy9aY051RHJVcWdRVVorcXRKNXFkRHNrVTkrNk5PUFh2em9Lbmg1UWpZCnNQSnJjK3FTbStlbVgvNzNjeXRidlFMV1RtaGkzWGtDN3BCcjRGTFhsUWkyQ3o1eFpzaEw2VlVqVGZSQXZJUlgKRUZvejE3ME92VDFGam1tcWZSSEdzVFVuSVBJckJJazkvV2FuT1h6dTBnYzMrVGxMUjd2RTlWbHZrUXpBanhFRwpnbXM3REUycTRETXFUSGN0YXBRZDByOUlET0xNUHFjbThNOExIeGd5RitmdExvcTIybWRVeXNTcWhRTjYwaWdmCm1OSjNDTTZ3L0xiU1ptYi9QajJtd1dBTQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg== + client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlKS0FJQkFBS0NBZ0VBeTJEeGFDUkdPZjFkOVdZdWxtM3RaWVlta2swKzJ4bXRSa1V0TnVYWmNXUmFzRGNaCmt6STBiemNCTk9WS1RNZDFmMVlrU3l6S1JLekFObkRBRWxHM1BVaCtqeG9oUU9SQXZ3bU1PZ1Bmc3pyUThVdzYKdmhTNG9hYUw4Zm11QVVLWjFFMUlwSG5YQmU1eFBRcityMFJFSWtxZFE2eGUydit5RmJvL0tDc0tieWR6VmtzbQpXcTFienNLS0NINWp3UHc5b3U0Q0hhWlVqN3lmaFBOSGI1dS9OWmdQcVJyQmhuby80dXhicmFFOTBnZG1MRUwvCmZGQjVuOGZLTFZsY21mVmxEcW9tTG8xTk5xL1o4ZGVmNmdpSFJlWFArTmprS1hVMjlFOGJtdW5TZEFnaWVwUm8KdjVzb1UvdjNkZGtMU2d6Y20yKzAxZ2xSRGk1LzdzMm4wOW0xaTBJVVZ3dUNvd2doOXlQS2YxREhZb3BYK1NoSQp6SDdNWVlTSktjMVRuMWwra3FrejAwakljTEwraGRDckRjWGdxYjJHc29PalR3NDNPTEJhQ1hNbmtibVN0Q3BZClBGRDMwY05uTlNqSmlDVm1YdFVzdmswQmthNmhVaVdhWmdvN0M0N3paZmFKczJNSjZtc0hNMGVBYnEvSHk3T0YKOUIrRlB6Rk9wd0ZyL2xsRE1OSnZLaDRaSGhTUkVqaVorMHZ5cmh2c0g2ekcyZHduWU9McUdVcFI1WHphREpLSwo5alFhQTBmU0s3Q3c1aDFieGlKK0dldHAvdGg5NDl1dHE2VkFMYjRVS1g2N2tLYWFJYXF3L281Y0R3clFZY1pNClJjUzFJUHpaZWFscGFrNmRLWFVmREFNelVrcGNXcEtiSTJ0bFlsSWVDc1BudUZrcFQ3WVZMWHBvWkhNQ0F3RUEKQVFLQ0FnQVNXeGFPdXE5bjduazdlOVI2bVVOUzVXKzN4cWNFcVdXZE1RTXFzRVlXMk9MaWdxYVpSVys4RmUvUwo4VnJxWVJvMnV2U2RvRlpGS241U3E0eDZXc3U2Q1QvNjlIWEo0SkFEYmY0dEFrcC9aN0NTQnJwS2k1NDZZTWpDCnR6RkFSZDJTYnNNK3JReCtLWTRvMzBRclYzTDYwVmN1VTNGZ1BMRzdDa3RHK2RPQVFzUkRKcGM0bWVydVYxcVQKOTZ4VEw3RDBjcHpZUmpuZG16MmVoMEtNVFRkTnhBS3QxWTlmN3NxelZYZlV6UGZDYkE2SnNpWlM5d1J6S2xvUwpDNzJ5MjVZYVk3cjhlcDVjY0RTd2dqQ1pzOWxac3FzNW40SEFTQ2NFbDlzNitSMllnVjl6UnMvSmMxeUpiL1ZlCnVvOHRTTW5rY1ltVnc5Ti8veXhteGtMK2VPak1sbTRiK1A0SUEvR0RUODRLT2NPUXdVaFNGam4zTXEvZTNVMS8KTm9CVEgwdnBqSEhCditzaVA1T2JYS2dKbFJDb0J6QzJUWkRLaDF5TGM2M25nQ2RMZGhjTzVJalVtc2pKbE1wZgozYmFQOHFjbitJNEw2K3NUT3pTdWNnenJURTlOQWl2aUlaS2ZPcDFwRFNETEJSbjFGbGY1Szl1cEkrdjZyWFFpCjFkLzI3M3lRbHF6UlN1VjJjMjhZeVVxR3pObFZRdk5CV09tRndrTW42RnlSb0ZsZVlpQW1WNTE2dzMzamtwbjEKN0FJM1pBOEZZd1VSQjZKTWh4cmdIZ2lLSHh4d3Nka2dTQ0FaZnlRaDk1bkNkTUFsRXlDdjZxbzdKck1GVDJwVApPMGs3eVVGQlNlV3hLS1dNTkRNV0IzbVc4Um9qRmZaVTM2ZUZzZklsdUljczBNSzlvUUtDQVFFQS9DOVV6cUlQCm1Tb2hSQkJLa1RVeGZLMHN4cStoM3NBYlg5cUg1bVNaU1JDZjhvUklNZXVLSzVsNWl2bzJ6amh2aDFwZlZZbDEKVWFsZ3RuOWJNZDQvNGprVGpYYUxGSVhtWmpzYUVCUE5rOHY0MHpSaVhDVElPNmg1MytsZ0t3YW1UMEo0SXhpUQpDang4RFhVWTVxeDU3eFdaWlgrV1VmeGV2YnQxUXhrZ3J2aitaVE02Y0hQMUJJVUQvclgvUWV1QnpkTG5qbmNZCmZTQTRmK2E2eFFXL1ExZlk4ODhmSkpINmpWMlZ4Rk9XalE1eS95eEtseVdxYUVpZ2dtdkczMGVxRGZYNTZKVm0KeldIS0dUUk1oYlFXd0RrbDczSnc3Ym51bXlJK3J5bGRYOE9Bc2gvZk1LR21HbVMrQXQxdDU3Z3A3K2FLKzhkaQpLdU54clZBbk1GeEtjUUtDQVFFQXpuU1grMFBRNzJaUlZtS21VckFwZkdxNmNKSWpCZUhoRTlTbkxxc1hPQ2lwCjhlRVhIUTZVRittSHNkOThLWFk4TllrQ1J6TDk5SUw5RHB6MHRrZlNVb0IrVHovb0M0V1lpM0M0SEtFNmxNSVAKSFVkcEpJbm1UZmk2NVpnMVlobWlnelpZOTR4SWVIKzNEV0ZHNnU5TS9MajRHY3NkejJQNTk4SnhmbjZoUlRXNAo3Zzk5UWtkUWxPUng3UTR0cWpObWRmcnBGUTRvVE1SLzRFbWM0L05aeXZMczNpVjRQR0pndlB4Zi8zejViTXQzClVXK3J1UTBrSHQ0c3JJcm1ZOG14cnBGTjlVTDEvU2lLVGNRYXlzaUdHNE04ajAyUnpXaXh2RHJGNFJXaUdPcVoKOUJPdjhoYms4Q2tHOWxTbW83TkZjeW9XNmxmdm9YS0EwdGoyTVNNbkl3S0NBUUFOM3ppbUNTb2NmY0hJc2JDNApYaG16eHY3Z2l3YTNidy9TYlAzdXdYZmhHZ1lDWEI1d2lJbFlMcUFOOGdlVHZtejhJQStBY0FoQkRyVWdOTTg2ClpnUlRtRm9uL081WG1hbjcwNkp5WWZSNy9jdFpLdGVDUkErem9HZmIxa1dRK0ZPcnBuZGZUdG1WaHBBNFdVbWUKWHJ4eVYxalJ5WHZtVktDTmhVTkZCcHZTcENXeVFXZ2tpV3hmN0VVMC9LUG01MktEaE9keDRjRTZHTXlpMUhsUApueXBQTlFUZEhIYVZGMmFWNGlENzVMRXlYeW9hT1VCOUlmaEd0a0FHREpHcTlKRTVKVit5MkkwSUhTbjF5dmV1Cm55SmxQbitFdFdIYTRkQnJQaTMraXFMMVFwYlF0WW9jYmJmcVdjSk4vQW93cGVQQXE0alhvcmNXcnQ0dzcyakkKY3pzeEFvSUJBR2c1aWlSSjV5ZUVjdGwyY2RyYW5nYjJabXJBQzdoRGdQb2U1OHR5aWdWSzJnaUhCc0VmZy9uUwpaak9RekVDdU5OVWQ5cTEwKzZtOW12ME9sYWVUK0lTMzQ0d2tpZ09QRFNTUDRoSnAvb1hzdGU2bEIxcHYxM2xUCmNSN0pTV0hkNlNIdWhObHIwNFlwaTRMUXNobVdkem82TXQzcW9ZRDZTczRlTm1lU2k5M0JzanRVVEhyeVA2YjYKOE55b3hMcmd1Z0FqakJtYURSUThPd1pDZzFhOG1uOFBSaThib1doeWpiMjZkc2duRWxBdkkyeFVRTS9wNTJ0Qgo1REd5Nlg1RTYxTmlmWVdzcVR5Rmc5U2doSm9NdzA4T1lMdERnTEMvQXNNd3RJYmVBZmdzTDA1OXhkd2tYSzAxCk9ST1FScFBydzhyUTI4S3daQVFRSnlMS2J2YjlkUnNDZ2dFQkFOQ3lhODU0Sk0rbXo3U2xvOFBIdnJ3Z3c4ai8KZVdsZEVudFNIQlFKTjJ5ME5hK2tWQ1ozN1ZiM29KK3RDQzBCa1p2RGNYMjZ2em1jd0FHaFBRMUpNL25vMjdEYgpmcWxJVW9wOUc1a0lQOE96TDJQclp6N0VHVmc1ZEFlQ2QwWDZIZFpGemxrajUzNEhiUzhSQjlybTlLYjhBV3BuCmFrQjNZR1NEM1hSbUdhR1lkQTU5WGxxNDBadmg1MzkrRFJibForcnI5NFpZbGU0V1B0TFR1Vjl2UnRpSFA5RDMKREZmTThPRzBNaVlCaTRHb3hnRHZDeVBoK2hjTWdmVUZwSDFiTUpMaXFGL2prRjJicis2d04wcEFuYlRET0o0egpVeHZvajdpVVlieXh6THdsc0VVSDJWc3NQdzQzMFR4ZnVrbyt2M3VtM3RramdqS3R2NVlmeGw4eERhYz0KLS0tLS1FTkQgUlNBIFBSSVZBVEUgS0VZLS0tLS0K + token: kcoxto5qodsxd40uyjdaylln67got5qpj6edfhx545vlv71n4m1rvhduyvshjlwoo4thqchr6z3m1l4mkgrjnnt1c9qkby2to3rq1s34fhzyg3yurfvcq8au9bvoycgm