From 7d4fb7ce57172aff96e9ae4868150bae6a771ede Mon Sep 17 00:00:00 2001 From: Dmitriy Creed Date: Sun, 25 Feb 2024 20:18:37 +0700 Subject: [PATCH 01/20] Lab7 & Lab8 Signed-off-by: Dmitriy Creed --- lab7.md | 68 ++++++++++++++++++++++++++++++++++++++++++++++++ lab8.md | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 148 insertions(+) create mode 100644 lab7.md create mode 100644 lab8.md diff --git a/lab7.md b/lab7.md new file mode 100644 index 0000000000..f00bad031a --- /dev/null +++ b/lab7.md @@ -0,0 +1,68 @@ +# Lab 7: Monitoring and Logging + +## Overview + +In this lab, you will become familiar with a logging stack that includes Promtail, Loki, and Grafana. Your goal is to create a Docker Compose configuration and configuration files to set up this logging stack. + +## Task 1: Logging Stack Setup + +**6 Points:** + +1. Study the Logging Stack: + - Begin by researching the components of the logging stack: + - [Grafana Webinar: Loki Getting Started](https://grafana.com/go/webinar/loki-getting-started/) + - [Loki Overview](https://grafana.com/docs/loki/latest/overview/) + - [Loki GitHub Repository](https://github.com/grafana/loki) + +2. Create a Monitoring Folder: + - Start by creating a new folder named `monitoring` in your project directory. + +3. Docker Compose Configuration: + - Inside the `monitoring` folder, prepare a `docker-compose.yml` file that defines the entire logging stack along with your application. + - To assist you in this task, refer to these resources for sample Docker Compose configurations: + - [Example Docker Compose Configuration from Loki Repository](https://github.com/grafana/loki/blob/main/production/docker-compose.yaml) + - [Promtail Configuration Example](https://github.com/black-rosary/loki-nginx/blob/master/promtail/promtail.yml) (Adapt it as needed) + +4. Testing: + - Verify that the configured logging stack and your application work as expected. + +## Task 2: Documentation and Reporting + +**6 Points:** + +1. Logging Stack Report: + - Create a new file named `LOGGING.md` to document how the logging stack you've set up functions. + - Provide detailed explanations of each component's role within the stack. + +2. Screenshots: + - Capture screenshots that demonstrate the successful operation of your logging stack. + - Include these screenshots in your `LOGGING.md` report for reference. + +3. Pull Request to Forked Repository: + - Initiate a Pull Request (PR) to the main branch of the forked repository. + - Request that your teammates review this PR, and also review PRs from your teammates. + +4. Pull Request in Your Repository: + - Create a PR in your own repository from the lab7 branch to the main branch. + - This step is essential for grading and tracking your work. + +## Bonus Task: Additional Configuration + +**2.5 Points:** + +1. Integrating Your Extra App: + - Extend the `docker-compose.yml` configuration to include your additional application. + +2. Configure Stack for Comprehensive Logging: + - Modify the logging stack's configuration to collect logs from all containers defined in the `docker-compose.yml`. + - Include screenshots in your `LOGGING.md` report to demonstrate your success. + +### Guidelines + +- Ensure that your documentation in `LOGGING.md` is well-structured and comprehensible. +- Follow proper naming conventions for files and folders. +- Use code blocks and Markdown formatting where appropriate. +- Participate actively in the peer review process by submitting and reviewing PRs. +- When creating the PR in your repository, make it from the lab4 branch to the lab3 branch. + +> Note: Thoroughly document your work, and ensure the logging stack functions correctly. Utilize the bonus points opportunity to enhance your understanding and the completeness of your setup. diff --git a/lab8.md b/lab8.md new file mode 100644 index 0000000000..a6c593b894 --- /dev/null +++ b/lab8.md @@ -0,0 +1,80 @@ +# Lab 8: Monitoring with Prometheus + +## Overview + +In this lab, you will become acquainted with Prometheus, set it up, and configure applications to collect metrics. + +## Task 1: Prometheus Setup + +**6 Points:** + +1. Learn About Prometheus: + - Begin by reading about Prometheus and its fundamental concepts: + - [Prometheus Overview](https://prometheus.io/docs/introduction/overview/) + - [Prometheus Naming Best Practices](https://prometheus.io/docs/practices/naming/) + +2. Integration with Docker Compose: + - Expand your existing `docker-compose.yml` file from the previous lab to include Prometheus. + +3. Prometheus Configuration: + - Configure Prometheus to collect metrics from both Loki and Prometheus containers. + +4. Verify Prometheus Targets: + - Access `http://localhost:9090/targets` to ensure that Prometheus is correctly scraping metrics. + - Capture screenshots that confirm the successful setup and place them in a file named `METRICS.md` within the monitoring folder. + +5. Pull Request to Forked Repository: + - Create a Pull Request (PR) to the main branch of the forked repository. + - Request your teammates to review this PR, and actively review PRs from your teammates. + +6. Pull Request in Your Repository: + - Construct a PR in your repository, linking the lab8 branch to the main branch. + - This step is essential for grading and monitoring your work. + +## Task 2: Dashboard and Configuration Enhancements + +**4 Points:** + +1. Grafana Dashboards: + - Set up dashboards in Grafana for both Loki and Prometheus. + - You can use examples as references: + - [Example Dashboard for Loki](https://grafana.com/grafana/dashboards/13407) + - [Example Dashboard for Prometheus](https://grafana.com/grafana/dashboards/3662) + - Capture screenshots displaying your successful dashboard configurations and include them in `METRICS.md`. + +2. Service Configuration Updates: + - Enhance the configuration of all services in the `docker-compose.yml` file: + - Add log rotation mechanisms. + - Specify memory limits for containers. + - Ensure these changes are documented within your `METRICS.md` file. + +3. Metrics Gathering: + - Extend Prometheus to gather metrics from all services defined in the `docker-compose.yml` file. + +## Bonus Task: Metrics and Health Checks + +**To Earn 2.5 Additional Points:** + +1. Application Metrics: + - Integrate metrics into your applications. You can refer to Python examples like: + - [Monitoring a Synchronous Python Web Application](https://dzone.com/articles/monitoring-your-synchronous-python-web-application) + - [Metrics Monitoring in Python](https://opensource.com/article/18/4/metrics-monitoring-and-python) + +2. Obtain Application Metrics: + - Configure your applications to export metrics. + +3. METRICS.md Update: + - Document your progress with the bonus tasks, including screenshots, in the `METRICS.md` file. + +4. Health Checks: + - Further enhance the `docker-compose.yml` file's service configurations by adding health checks for the containers. + +### Guidelines + +- Maintain a well-structured and comprehensible `METRICS.md` document. +- Adhere to file and folder naming conventions. +- Utilize code blocks and Markdown formatting where appropriate. +- Engage actively in the peer review process by submitting and reviewing PRs. +- When creating the PR in your repository, make it from the lab4 branch to the lab3 branch. + +> Note: Ensure thorough documentation of your work, and guarantee that Prometheus correctly collects metrics. Take advantage of the bonus tasks to deepen your understanding and enhance the completeness of your setup. From a9b2d128068d9c7b7454851a2f031970605bb875 Mon Sep 17 00:00:00 2001 From: Dmitriy Creed Date: Sun, 3 Mar 2024 21:42:06 +0700 Subject: [PATCH 02/20] fix Signed-off-by: Dmitriy Creed --- lab4.md | 3 ++- lab5.md | 2 +- lab7.md | 11 +---------- lab8.md | 11 +---------- 4 files changed, 5 insertions(+), 22 deletions(-) diff --git a/lab4.md b/lab4.md index d5eefd0808..93ea4795bc 100644 --- a/lab4.md +++ b/lab4.md @@ -52,7 +52,7 @@ In this lab assignment, you will explore Infrastructure as Code (IAC) using Terr - Avoid placing your token as a variable in the code; instead, use an environment variable. 2. Import Existing Repository: - - Use the `terraform import` command to import your existing GitHub repository into your Terraform configuration. Example: `terraform import "github_repository.core-course-labs" "core-course-labs"`. + - Use the `terraform import` command to import your current GitHub repository into your Terraform configuration. No need to create a new one. Example: `terraform import "github_repository.core-course-labs" "core-course-labs"`. 3. Apply Terraform Changes: - Apply changes from your Terraform configuration to your GitHub repository. @@ -65,6 +65,7 @@ In this lab assignment, you will explore Infrastructure as Code (IAC) using Terr **2.5 Points:** 1. GitHub Teams Using Terraform: + - You need to upgrade your account to organization. - Extend your Terraform configuration to add several teams to your GitHub repository, each with different levels of access. - Apply the changes and ensure they take effect in your GitHub repository. diff --git a/lab5.md b/lab5.md index 63e961624d..7be6eee43b 100644 --- a/lab5.md +++ b/lab5.md @@ -75,7 +75,7 @@ In this lab, you will get acquainted with Ansible, a powerful configuration mana - Use a Markdown template to describe your Docker role, its requirements and usage. 3. Deployment Output: - - Execute your playbook to deploy the Docker role (either custom or existing). + - Execute your playbook to deploy the Docker role. - Provide the last 50 lines of the output from your deployment command in the `ANSIBLE.md` file. Example command: diff --git a/lab7.md b/lab7.md index f00bad031a..617c232808 100644 --- a/lab7.md +++ b/lab7.md @@ -38,14 +38,6 @@ In this lab, you will become familiar with a logging stack that includes Promtai - Capture screenshots that demonstrate the successful operation of your logging stack. - Include these screenshots in your `LOGGING.md` report for reference. -3. Pull Request to Forked Repository: - - Initiate a Pull Request (PR) to the main branch of the forked repository. - - Request that your teammates review this PR, and also review PRs from your teammates. - -4. Pull Request in Your Repository: - - Create a PR in your own repository from the lab7 branch to the main branch. - - This step is essential for grading and tracking your work. - ## Bonus Task: Additional Configuration **2.5 Points:** @@ -62,7 +54,6 @@ In this lab, you will become familiar with a logging stack that includes Promtai - Ensure that your documentation in `LOGGING.md` is well-structured and comprehensible. - Follow proper naming conventions for files and folders. - Use code blocks and Markdown formatting where appropriate. -- Participate actively in the peer review process by submitting and reviewing PRs. -- When creating the PR in your repository, make it from the lab4 branch to the lab3 branch. +- Create pull requests (PRs) as needed: from your fork to the main branch of this repository, and from your fork's branch to your fork's master branch. > Note: Thoroughly document your work, and ensure the logging stack functions correctly. Utilize the bonus points opportunity to enhance your understanding and the completeness of your setup. diff --git a/lab8.md b/lab8.md index a6c593b894..8eb0752ec7 100644 --- a/lab8.md +++ b/lab8.md @@ -23,14 +23,6 @@ In this lab, you will become acquainted with Prometheus, set it up, and configur - Access `http://localhost:9090/targets` to ensure that Prometheus is correctly scraping metrics. - Capture screenshots that confirm the successful setup and place them in a file named `METRICS.md` within the monitoring folder. -5. Pull Request to Forked Repository: - - Create a Pull Request (PR) to the main branch of the forked repository. - - Request your teammates to review this PR, and actively review PRs from your teammates. - -6. Pull Request in Your Repository: - - Construct a PR in your repository, linking the lab8 branch to the main branch. - - This step is essential for grading and monitoring your work. - ## Task 2: Dashboard and Configuration Enhancements **4 Points:** @@ -74,7 +66,6 @@ In this lab, you will become acquainted with Prometheus, set it up, and configur - Maintain a well-structured and comprehensible `METRICS.md` document. - Adhere to file and folder naming conventions. - Utilize code blocks and Markdown formatting where appropriate. -- Engage actively in the peer review process by submitting and reviewing PRs. -- When creating the PR in your repository, make it from the lab4 branch to the lab3 branch. +- Create pull requests (PRs) as needed: from your fork to the main branch of this repository, and from your fork's branch to your fork's master branch. > Note: Ensure thorough documentation of your work, and guarantee that Prometheus correctly collects metrics. Take advantage of the bonus tasks to deepen your understanding and enhance the completeness of your setup. From 900b8c571560b9d72b188587e8f6f6593ee62f92 Mon Sep 17 00:00:00 2001 From: Ahmad Sarhan Date: Mon, 4 Mar 2024 17:25:46 +0300 Subject: [PATCH 03/20] terraform --- .gitignore | 9 +- app_python/terraform/TF.md | 330 ++++++++++++++++++++++++ app_python/terraform/docker/main.tf | 25 ++ app_python/terraform/docker/outputs.tf | 9 + app_python/terraform/docker/variable.tf | 5 + app_python/terraform/github/main.tf | 43 +++ app_python/terraform/github/variable.tf | 7 + app_python/terraform/yandex/main.tf | 85 ++++++ app_python/terraform/yandex/outputs.tf | 15 ++ app_python/terraform/yandex/variable.tf | 5 + 10 files changed, 532 insertions(+), 1 deletion(-) create mode 100644 app_python/terraform/TF.md create mode 100644 app_python/terraform/docker/main.tf create mode 100644 app_python/terraform/docker/outputs.tf create mode 100644 app_python/terraform/docker/variable.tf create mode 100644 app_python/terraform/github/main.tf create mode 100644 app_python/terraform/github/variable.tf create mode 100644 app_python/terraform/yandex/main.tf create mode 100644 app_python/terraform/yandex/outputs.tf create mode 100644 app_python/terraform/yandex/variable.tf diff --git a/.gitignore b/.gitignore index 525b230c8f..9029a898c4 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,11 @@ dist/ build/ *.egg-info/ -app_cpp/ \ No newline at end of file +app_cpp/ + +.terraform +.terraform.lock.hcl +terraform.tfstate +authorized_key.json +terraform.tfstate.backup +config.auto.tfvars \ No newline at end of file diff --git a/app_python/terraform/TF.md b/app_python/terraform/TF.md new file mode 100644 index 0000000000..c7f69f33ff --- /dev/null +++ b/app_python/terraform/TF.md @@ -0,0 +1,330 @@ +## Docker +1. **`terraform state list`** + ``` + docker_container.nginx + docker_image.nginx + ``` + +2. **`terraform state show docker_container.nginx`** + ``` + resource "docker_container" "nginx" { + attach = false + command = [ + "nginx", + "-g", + "daemon off;", + ] + container_read_refresh_timeout_milliseconds = 15000 + cpu_shares = 0 + entrypoint = [ + "/docker-entrypoint.sh", + ] + env = [] + hostname = "2c3401053cc5" + id = "2c3401053cc5a9f9dfcaf3d2c07c187b0bbe5c84c070881872ee936959bec312" + image = "sha256:e4720093a3c1381245b53a5a51b417963b3c4472d3f47fc301930a4f3b17666a" + init = false + ipc_mode = "private" + log_driver = "json-file" + logs = false + max_retry_count = 0 + memory = 0 + memory_swap = 0 + must_run = true + name = "tutorial" + network_data = [ + { + gateway = "172.17.0.1" + global_ipv6_address = "" + global_ipv6_prefix_length = 0 + ip_address = "172.17.0.2" + ip_prefix_length = 16 + ipv6_gateway = "" + mac_address = "02:42:ac:11:00:02" + network_name = "bridge" + }, + ] + network_mode = "default" + privileged = false + publish_all_ports = false + read_only = false + remove_volumes = true + restart = "no" + rm = false + runtime = "runc" + security_opts = [] + shm_size = 64 + start = true + stdin_open = false + stop_signal = "SIGQUIT" + stop_timeout = 0 + tty = false + wait = false + wait_timeout = 60 + + ports { + external = 8000 + internal = 80 + ip = "0.0.0.0" + protocol = "tcp" + } + } + ``` + +3. **`terraform state show docker_image.nginx`** + ``` + resource "docker_image" "nginx" { + id = "sha256:e4720093a3c1381245b53a5a51b417963b3c4472d3f47fc301930a4f3b17666anginx:latest" + image_id = "sha256:e4720093a3c1381245b53a5a51b417963b3c4472d3f47fc301930a4f3b17666a" + keep_locally = false + name = "nginx:latest" + repo_digest = "nginx@sha256:c26ae7472d624ba1fafd296e73cecc4f93f853088e6a9c13c0d52f6ca5865107" + } + ``` + +4. **`terraform output`** + ``` + container_id = "3154af8b3156a3c55149041e6b001a63a530131c81e7c8b88d64e7a3087d5ee3" + image_id = "sha256:e4720093a3c1381245b53a5a51b417963b3c4472d3f47fc301930a4f3b17666anginx:latest" + ``` + +## Yandex + +1. **`terraform state list`** + ``` + yandex_compute_disk.boot-disk-1 + yandex_compute_disk.boot-disk-2 + yandex_compute_instance.vm-1 + yandex_compute_instance.vm-2 + yandex_vpc_network.network-1 + yandex_vpc_subnet.subnet-1 + ``` + +2. **`terraform state show yandex_compute_disk.boot-disk-1`** + ``` + # yandex_compute_disk.boot-disk-1: + resource "yandex_compute_disk" "boot-disk-1" { + block_size = 4096 + created_at = "2024-03-04T13:33:16Z" + folder_id = "b1gdfa5g164ijsjslt2f" + id = "epdfujo4qo2oc9a2g8q5" + image_id = "fd8adntm80abl0lh2pa8" + name = "boot-disk-1" + product_ids = [ + "f2emovtn3j6rb7e1vfg5", + ] + size = 20 + status = "ready" + type = "network-hdd" + zone = "ru-central1-b" + + disk_placement_policy {} + } + ``` + +3. **`terraform state show yandex_compute_disk.boot-disk-2`** + ``` + # yandex_compute_disk.boot-disk-2: + resource "yandex_compute_disk" "boot-disk-2" { + block_size = 4096 + created_at = "2024-03-04T13:33:16Z" + folder_id = "b1gdfa5g164ijsjslt2f" + id = "epdvmrp6b4jaj963m5m6" + image_id = "fd8adntm80abl0lh2pa8" + name = "boot-disk-2" + product_ids = [ + "f2emovtn3j6rb7e1vfg5", + ] + size = 20 + status = "ready" + type = "network-hdd" + zone = "ru-central1-b" + + disk_placement_policy {} + } + ``` + +4. **`terraform state show yandex_compute_instance.vm-1`** + ``` + # yandex_compute_instance.vm-1: + resource "yandex_compute_instance" "vm-1" { + created_at = "2024-03-04T13:33:26Z" + folder_id = "b1gdfa5g164ijsjslt2f" + fqdn = "epdtvbocd08ep105lmit.auto.internal" + id = "epdtvbocd08ep105lmit" + metadata = { + "ssh-keys" = <<-EOT + ubuntu:ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICrrApeAaCsWZVLgYst1TqSeIHs63hWVgkD4jv7+wjPT sarhan@sarhan-HP + EOT + } + name = "terraform1" + network_acceleration_type = "standard" + platform_id = "standard-v1" + status = "running" + zone = "ru-central1-b" + + boot_disk { + auto_delete = true + device_name = "epdfujo4qo2oc9a2g8q5" + disk_id = "epdfujo4qo2oc9a2g8q5" + mode = "READ_WRITE" + + initialize_params { + block_size = 4096 + image_id = "fd8adntm80abl0lh2pa8" + name = "boot-disk-1" + size = 20 + type = "network-hdd" + } + } + + metadata_options { + aws_v1_http_endpoint = 1 + aws_v1_http_token = 2 + gce_http_endpoint = 1 + gce_http_token = 1 + } + + network_interface { + index = 0 + ip_address = "192.168.10.21" + ipv4 = true + ipv6 = false + mac_address = "d0:0d:1d:fa:f0:c6" + nat = true + nat_ip_address = "84.201.140.230" + nat_ip_version = "IPV4" + security_group_ids = [] + subnet_id = "e2l9h9iskuprghf4574k" + } + + placement_policy { + host_affinity_rules = [] + placement_group_partition = 0 + } + + resources { + core_fraction = 100 + cores = 2 + gpus = 0 + memory = 2 + } + + scheduling_policy { + preemptible = false + } + } + ``` + +5. **`terraform state show yandex_compute_instance.vm-2`** + ``` + # yandex_compute_instance.vm-2: + resource "yandex_compute_instance" "vm-2" { + created_at = "2024-03-04T13:33:25Z" + folder_id = "b1gdfa5g164ijsjslt2f" + fqdn = "epdjseb162u4badp1vj4.auto.internal" + id = "epdjseb162u4badp1vj4" + metadata = { + "ssh-keys" = <<-EOT + ubuntu:ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICrrApeAaCsWZVLgYst1TqSeIHs63hWVgkD4jv7+wjPT sarhan@sarhan-HP + EOT + } + name = "terraform2" + network_acceleration_type = "standard" + platform_id = "standard-v1" + status = "running" + zone = "ru-central1-b" + + boot_disk { + auto_delete = true + device_name = "epdvmrp6b4jaj963m5m6" + disk_id = "epdvmrp6b4jaj963m5m6" + mode = "READ_WRITE" + + initialize_params { + block_size = 4096 + image_id = "fd8adntm80abl0lh2pa8" + name = "boot-disk-2" + size = 20 + type = "network-hdd" + } + } + + metadata_options { + aws_v1_http_endpoint = 1 + aws_v1_http_token = 2 + gce_http_endpoint = 1 + gce_http_token = 1 + } + + network_interface { + index = 0 + ip_address = "192.168.10.15" + ipv4 = true + ipv6 = false + mac_address = "d0:0d:13:e3:96:13" + nat = true + nat_ip_address = "84.201.153.200" + nat_ip_version = "IPV4" + security_group_ids = [] + subnet_id = "e2l9h9iskuprghf4574k" + } + + placement_policy { + host_affinity_rules = [] + placement_group_partition = 0 + } + + resources { + core_fraction = 100 + cores = 2 + gpus = 0 + memory = 2 + } + + scheduling_policy { + preemptible = false + } + } + ``` + +7. **`terraform state show yandex_vpc_network.network-1`** + ``` + # yandex_vpc_network.network-1: + resource "yandex_vpc_network" "network-1" { + created_at = "2024-03-04T13:28:10Z" + default_security_group_id = "enpah66ruo6b9ggmi62b" + folder_id = "b1gdfa5g164ijsjslt2f" + id = "enp9o7dfbur6qatnoo3k" + labels = {} + name = "network1" + subnet_ids = [ + "e9bvpeq4k5bf5718ffd2", + ] + } + ``` +8. **`terraform state show yandex_vpc_subnet.subnet-1`** + ``` + # yandex_vpc_subnet.subnet-1: + resource "yandex_vpc_subnet" "subnet-1" { + created_at = "2024-03-04T13:32:58Z" + folder_id = "b1gdfa5g164ijsjslt2f" + id = "e2l9h9iskuprghf4574k" + labels = {} + name = "subnet1" + network_id = "enp9o7dfbur6qatnoo3k" + v4_cidr_blocks = [ + "192.168.10.0/24", + ] + v6_cidr_blocks = [] + zone = "ru-central1-b" + } + ``` + +9. **`terraform output`** + ``` + external_ip_address_vm_1 = "84.201.140.230" + external_ip_address_vm_2 = "84.201.153.200" + internal_ip_address_vm_1 = "192.168.10.21" + internal_ip_address_vm_2 = "192.168.10.15" + ``` \ No newline at end of file diff --git a/app_python/terraform/docker/main.tf b/app_python/terraform/docker/main.tf new file mode 100644 index 0000000000..2846afed19 --- /dev/null +++ b/app_python/terraform/docker/main.tf @@ -0,0 +1,25 @@ +terraform { + required_providers { + docker = { + source = "kreuzwerker/docker" + version = "~> 3.0.1" + } + } +} + +provider "docker" {} + +resource "docker_image" "nginx" { + name = "nginx:latest" + keep_locally = false +} + +resource "docker_container" "nginx" { + image = docker_image.nginx.image_id + name = var.container_name + ports { + internal = 80 + external = 8080 + } +} + diff --git a/app_python/terraform/docker/outputs.tf b/app_python/terraform/docker/outputs.tf new file mode 100644 index 0000000000..96eb698a95 --- /dev/null +++ b/app_python/terraform/docker/outputs.tf @@ -0,0 +1,9 @@ +output "container_id" { + description = "ID of the Docker container" + value = docker_container.nginx.id +} + +output "image_id" { + description = "ID of the Docker image" + value = docker_image.nginx.id +} diff --git a/app_python/terraform/docker/variable.tf b/app_python/terraform/docker/variable.tf new file mode 100644 index 0000000000..a1e71ccf41 --- /dev/null +++ b/app_python/terraform/docker/variable.tf @@ -0,0 +1,5 @@ +variable "container_name" { + description = "Value of the name for the Docker container" + type = string + default = "ExampleNginxContainer" +} diff --git a/app_python/terraform/github/main.tf b/app_python/terraform/github/main.tf new file mode 100644 index 0000000000..47f5c6cbd5 --- /dev/null +++ b/app_python/terraform/github/main.tf @@ -0,0 +1,43 @@ +### Main.tf ### + +terraform { + required_version = "~> 1.7.4" + required_providers { + github = { + source = "integrations/github" + version = "~> 4.0" + } + } +} + +provider "github" { + token = var.token # or `GITHUB_TOKEN` +} + +#Create and initialise a public GitHub Repository with MIT license and a Visual Studio .gitignore file (incl. issues and wiki) +resource "github_repository" "repo" { + name = "Devops" + description = "Lab4" + visibility = "public" + has_issues = true + has_wiki = true + auto_init = true +} + +#Set default branch 'master' +resource "github_branch_default" "master" { + repository = github_repository.repo.name + branch = "main" +} + +#Create branch protection rule to protect the default branch. (Use "github_branch_protection_v3" resource for Organisation rules) +resource "github_branch_protection" "default" { + repository_id = github_repository.repo.id + pattern = github_branch_default.master.branch + require_conversation_resolution = true + enforce_admins = true + + required_pull_request_reviews { + required_approving_review_count = 1 + } +} diff --git a/app_python/terraform/github/variable.tf b/app_python/terraform/github/variable.tf new file mode 100644 index 0000000000..7045c0ba37 --- /dev/null +++ b/app_python/terraform/github/variable.tf @@ -0,0 +1,7 @@ +### Variables.tf ### + +variable "token" { + type = string + description = "Specifies the GitHub PAT token or `GITHUB_TOKEN`" + sensitive = true +} diff --git a/app_python/terraform/yandex/main.tf b/app_python/terraform/yandex/main.tf new file mode 100644 index 0000000000..4e8eb756f5 --- /dev/null +++ b/app_python/terraform/yandex/main.tf @@ -0,0 +1,85 @@ +terraform { + required_providers { + yandex = { + source = "yandex-cloud/yandex" + } + } + required_version = ">= 0.13" +} + +provider "yandex" { + zone = var.time_zone +} + +resource "yandex_compute_disk" "boot-disk-1" { + name = "boot-disk-1" + type = "network-hdd" + zone = var.time_zone + size = "20" + image_id = "fd8adntm80abl0lh2pa8" +} + +resource "yandex_compute_disk" "boot-disk-2" { + name = "boot-disk-2" + type = "network-hdd" + zone = var.time_zone + size = "20" + image_id = "fd8adntm80abl0lh2pa8" +} + +resource "yandex_compute_instance" "vm-1" { + name = "terraform1" + + resources { + cores = 2 + memory = 2 + } + + boot_disk { + disk_id = yandex_compute_disk.boot-disk-1.id + } + + network_interface { + subnet_id = yandex_vpc_subnet.subnet-1.id + nat = true + } + + metadata = { + ssh-keys = "ubuntu:${file("~/.ssh/id_ed25519.pub")}" + } +} + +resource "yandex_compute_instance" "vm-2" { + name = "terraform2" + + resources { + cores = 2 + memory = 2 + } + + boot_disk { + disk_id = yandex_compute_disk.boot-disk-2.id + } + + network_interface { + subnet_id = yandex_vpc_subnet.subnet-1.id + nat = true + } + + metadata = { + ssh-keys = "ubuntu:${file("~/.ssh/id_ed25519.pub")}" + } +} + +resource "yandex_vpc_network" "network-1" { + name = "network1" +} + +resource "yandex_vpc_subnet" "subnet-1" { + name = "subnet1" + zone = var.time_zone + network_id = yandex_vpc_network.network-1.id + v4_cidr_blocks = ["192.168.10.0/24"] +} + + diff --git a/app_python/terraform/yandex/outputs.tf b/app_python/terraform/yandex/outputs.tf new file mode 100644 index 0000000000..f73340783e --- /dev/null +++ b/app_python/terraform/yandex/outputs.tf @@ -0,0 +1,15 @@ +output "internal_ip_address_vm_1" { + value = yandex_compute_instance.vm-1.network_interface.0.ip_address +} + +output "internal_ip_address_vm_2" { + value = yandex_compute_instance.vm-2.network_interface.0.ip_address +} + +output "external_ip_address_vm_1" { + value = yandex_compute_instance.vm-1.network_interface.0.nat_ip_address +} + +output "external_ip_address_vm_2" { + value = yandex_compute_instance.vm-2.network_interface.0.nat_ip_address +} \ No newline at end of file diff --git a/app_python/terraform/yandex/variable.tf b/app_python/terraform/yandex/variable.tf new file mode 100644 index 0000000000..6323ccc367 --- /dev/null +++ b/app_python/terraform/yandex/variable.tf @@ -0,0 +1,5 @@ +variable "time_zone" { + description = "Yandex timezone" + type = string + default = "ru-central1-b" +} \ No newline at end of file From 7b3107e31cc027f26613362f4b13434bf1c3bd72 Mon Sep 17 00:00:00 2001 From: Ahmad Sarhan Date: Wed, 6 Mar 2024 03:42:02 +0300 Subject: [PATCH 04/20] move terraform out of python_app& ansible --- ansible/ANSIBLE.md | 120 ++++++++++++++++++ ansible/ansible.cfg | 3 + ansible/inventory/yandex_cloud.yml | 4 + ansible/playbooks/dev/main.yml | 5 + ansible/roles/docker/README.md | 17 +++ ansible/roles/docker/defaults/main.yml | 58 +++++++++ ansible/roles/docker/handlers/main.yml | 7 + .../roles/docker/tasks/install_compose.yml | 0 ansible/roles/docker/tasks/install_docker.yml | 0 ansible/roles/docker/tasks/main.yml | 50 ++++++++ {app_python/terraform => terraform}/TF.md | 0 .../terraform => terraform}/docker/main.tf | 0 .../terraform => terraform}/docker/outputs.tf | 0 .../docker/variable.tf | 0 .../terraform => terraform}/github/main.tf | 0 .../github/variable.tf | 2 +- .../terraform => terraform}/yandex/main.tf | 4 +- .../terraform => terraform}/yandex/outputs.tf | 0 .../yandex/variable.tf | 0 19 files changed, 267 insertions(+), 3 deletions(-) create mode 100644 ansible/ANSIBLE.md create mode 100644 ansible/ansible.cfg create mode 100644 ansible/inventory/yandex_cloud.yml create mode 100644 ansible/playbooks/dev/main.yml create mode 100644 ansible/roles/docker/README.md create mode 100644 ansible/roles/docker/defaults/main.yml create mode 100644 ansible/roles/docker/handlers/main.yml create mode 100644 ansible/roles/docker/tasks/install_compose.yml create mode 100644 ansible/roles/docker/tasks/install_docker.yml create mode 100644 ansible/roles/docker/tasks/main.yml rename {app_python/terraform => terraform}/TF.md (100%) rename {app_python/terraform => terraform}/docker/main.tf (100%) rename {app_python/terraform => terraform}/docker/outputs.tf (100%) rename {app_python/terraform => terraform}/docker/variable.tf (100%) rename {app_python/terraform => terraform}/github/main.tf (100%) rename {app_python/terraform => terraform}/github/variable.tf (85%) rename {app_python/terraform => terraform}/yandex/main.tf (95%) rename {app_python/terraform => terraform}/yandex/outputs.tf (100%) rename {app_python/terraform => terraform}/yandex/variable.tf (100%) diff --git a/ansible/ANSIBLE.md b/ansible/ANSIBLE.md new file mode 100644 index 0000000000..7d6d4ecdde --- /dev/null +++ b/ansible/ANSIBLE.md @@ -0,0 +1,120 @@ +# Ansible lab +## Inventory +`ansible/inventory/yandex_cloud` file contains ip address for vm where I am installing docker and docker-compose + +## Playbooks +`ansible/playbooks/dev/main.yml` runs the docker role which installs docker and docke-compose in the VM. + +## commands output + +### **`ansible-playbook playbooks/dev/main.yml --diff`** +``` + PLAY [Run Docker role] **************************************************************************************************** + + TASK [Gathering Facts] **************************************************************************************************** + ok: [51.250.102.185] + + TASK [docker : Update apt] ************************************************************************************************ + ok: [51.250.102.185] + + TASK [docker : Install pip] *********************************************************************************************** + The following additional packages will be installed: + binutils binutils-common binutils-x86-64-linux-gnu build-essential cpp cpp-9 + dpkg-dev fakeroot g++ g++-9 gcc gcc-10-base gcc-9 gcc-9-base + libalgorithm-diff-perl libalgorithm-diff-xs-perl libalgorithm-merge-perl + libasan5 libatomic1 libbinutils libc-dev-bin libc6 libc6-dev libcc1-0 + libcrypt-dev libctf-nobfd0 libctf0 libdpkg-perl libexpat1 libexpat1-dev + libfakeroot libfile-fcntllock-perl libgcc-9-dev libgcc-s1 libgdbm-compat4 + libgomp1 libisl22 libitm1 liblsan0 libmpc3 libmpfr6 libperl5.30 + libpython3-dev libpython3.8 libpython3.8-dev libpython3.8-minimal + libpython3.8-stdlib libquadmath0 libstdc++-9-dev libstdc++6 libtsan0 + libubsan1 linux-libc-dev make manpages-dev patch perl perl-base + perl-modules-5.30 python-pip-whl python3-dev python3-pkg-resources + python3-wheel python3.8 python3.8-dev python3.8-minimal zlib1g zlib1g-dev + Suggested packages: + binutils-doc cpp-doc gcc-9-locales debian-keyring g++-multilib + g++-9-multilib gcc-9-doc gcc-multilib autoconf automake libtool flex bison + gdb gcc-doc gcc-9-multilib glibc-doc git bzr libstdc++-9-doc make-doc + diffutils-doc perl-doc libterm-readline-gnu-perl + | libterm-readline-perl-perl libb-debug-perl liblocale-codes-perl + python-setuptools-doc python3.8-venv python3.8-doc binfmt-support + The following NEW packages will be installed: + binutils binutils-common binutils-x86-64-linux-gnu build-essential cpp cpp-9 + dpkg-dev fakeroot g++ g++-9 gcc gcc-9 gcc-9-base libalgorithm-diff-perl + libalgorithm-diff-xs-perl libalgorithm-merge-perl libasan5 libatomic1 + libbinutils libc-dev-bin libc6-dev libcc1-0 libcrypt-dev libctf-nobfd0 + libctf0 libdpkg-perl libexpat1-dev libfakeroot libfile-fcntllock-perl + libgcc-9-dev libgdbm-compat4 libgomp1 libisl22 libitm1 liblsan0 libmpc3 + libmpfr6 libperl5.30 libpython3-dev libpython3.8-dev libquadmath0 + libstdc++-9-dev libtsan0 libubsan1 linux-libc-dev make manpages-dev patch + perl perl-modules-5.30 python-pip-whl python3-dev python3-pip python3-wheel + python3.8-dev zlib1g-dev + The following packages will be upgraded: + gcc-10-base libc6 libexpat1 libgcc-s1 libpython3.8 libpython3.8-minimal + libpython3.8-stdlib libstdc++6 perl-base python3-pkg-resources + python3-setuptools python3.8 python3.8-minimal zlib1g + 14 upgraded, 56 newly installed, 0 to remove and 180 not upgraded. + changed: [51.250.102.185] + + TASK [docker : Install Docker dependencies] ******************************************************************************* + ok: [51.250.102.185] + + TASK [docker : Add Docker GPG key] **************************************************************************************** + changed: [51.250.102.185] + + TASK [docker : Add Docker repository] ************************************************************************************* + --- before: /dev/null + +++ after: /etc/apt/sources.list.d/docker.list + @@ -0,0 +1 @@ + +deb https://download.docker.com/linux/ubuntu bionic stable + + changed: [51.250.102.185] + + TASK [docker : Install Docker] ******************************************************************************************** + The following additional packages will be installed: + containerd.io docker-buildx-plugin docker-ce-cli docker-ce-rootless-extras + docker-compose-plugin git git-man libcurl3-gnutls liberror-perl pigz + slirp4netns + Suggested packages: + aufs-tools cgroupfs-mount | cgroup-lite git-daemon-run | git-daemon-sysvinit + git-doc git-el git-email git-gui gitk gitweb git-cvs git-mediawiki git-svn + The following NEW packages will be installed: + containerd.io docker-buildx-plugin docker-ce docker-ce-cli + docker-ce-rootless-extras docker-compose-plugin git git-man libcurl3-gnutls + liberror-perl pigz slirp4netns + 0 upgraded, 12 newly installed, 0 to remove and 180 not upgraded. + changed: [51.250.102.185] + + TASK [docker : Upgrade pip] *********************************************************************************************** + changed: [51.250.102.185] + + TASK [docker : Install docker-compose] ************************************************************************************ + changed: [51.250.102.185] + + PLAY RECAP **************************************************************************************************************** + 51.250.102.185 : ok=9 changed=6 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 +``` + +### **`ansible-inventory -i inventory/yandex_cloud.yml --list`** +```json + { + "_meta": { + "hostvars": { + "51.250.102.185": { + "ansible_user": "ubuntu" + } + } + }, + "all": { + "children": [ + "myhost", + "ungrouped" + ] + }, + "myhost": { + "hosts": [ + "51.250.102.185" + ] + } + } +``` \ No newline at end of file diff --git a/ansible/ansible.cfg b/ansible/ansible.cfg new file mode 100644 index 0000000000..c83c2ba7bd --- /dev/null +++ b/ansible/ansible.cfg @@ -0,0 +1,3 @@ +[defaults] +inventory = ./inventory +roles_path = ./roles \ No newline at end of file diff --git a/ansible/inventory/yandex_cloud.yml b/ansible/inventory/yandex_cloud.yml new file mode 100644 index 0000000000..3d00f9ac3b --- /dev/null +++ b/ansible/inventory/yandex_cloud.yml @@ -0,0 +1,4 @@ +myhost: + hosts: + 51.250.102.185: + ansible_user: ubuntu \ No newline at end of file diff --git a/ansible/playbooks/dev/main.yml b/ansible/playbooks/dev/main.yml new file mode 100644 index 0000000000..7c65f4a3d5 --- /dev/null +++ b/ansible/playbooks/dev/main.yml @@ -0,0 +1,5 @@ +- name: Run Docker role + hosts: all + roles: + - role: docker + become: true \ No newline at end of file diff --git a/ansible/roles/docker/README.md b/ansible/roles/docker/README.md new file mode 100644 index 0000000000..4161f96a00 --- /dev/null +++ b/ansible/roles/docker/README.md @@ -0,0 +1,17 @@ +# Docker Role + +## Overview +It's an ansible role that installs docker in ubuntu + +## requirements + +1. `ubuntu` +2. `python` + +## installation details + +`main.yaml`: +1. Upgrade apt. +2. Install and upgrade pip. +3. Install docker using apt. +4. Install docker-compose using pip. diff --git a/ansible/roles/docker/defaults/main.yml b/ansible/roles/docker/defaults/main.yml new file mode 100644 index 0000000000..ccc3b1cdbd --- /dev/null +++ b/ansible/roles/docker/defaults/main.yml @@ -0,0 +1,58 @@ +--- +# Edition can be one of: 'ce' (Community Edition) or 'ee' (Enterprise Edition). +docker_edition: 'ce' +docker_packages: + - "docker-{{ docker_edition }}" + - "docker-{{ docker_edition }}-cli" + - "docker-{{ docker_edition }}-rootless-extras" + - "containerd.io" + - docker-buildx-plugin +docker_packages_state: present + +# Service options. +docker_service_manage: true +docker_service_state: started +docker_service_enabled: true +docker_restart_handler_state: restarted + +# Docker Compose Plugin options. +docker_install_compose_plugin: true +docker_compose_package: docker-compose-plugin +docker_compose_package_state: present + +# Docker Compose options. +docker_install_compose: false +docker_compose_version: "v2.11.1" +docker_compose_arch: "{{ ansible_architecture }}" +docker_compose_url: "https://github.com/docker/compose/releases/download/{{ docker_compose_version }}/docker-compose-linux-{{ docker_compose_arch }}" +docker_compose_path: /usr/local/bin/docker-compose + +# Enable repo setup +docker_add_repo: true + +# Docker repo URL. +docker_repo_url: https://download.docker.com/linux + +# Used only for Debian/Ubuntu/Pop!_OS/Linux Mint. Switch 'stable' to 'nightly' if needed. +docker_apt_release_channel: stable +# docker_apt_ansible_distribution is a workaround for Ubuntu variants which can't be identified as such by Ansible, +# and is only necessary until Docker officially supports them. +docker_apt_ansible_distribution: "{{ 'ubuntu' if ansible_distribution in ['Pop!_OS', 'Linux Mint'] else ansible_distribution }}" +docker_apt_arch: "{{ 'arm64' if ansible_architecture == 'aarch64' else 'amd64' }}" +docker_apt_repository: "deb [arch={{ docker_apt_arch }} signed-by=/etc/apt/trusted.gpg.d/docker.asc] {{ docker_repo_url }}/{{ docker_apt_ansible_distribution | lower }} {{ ansible_distribution_release }} {{ docker_apt_release_channel }}" +docker_apt_ignore_key_error: true +docker_apt_gpg_key: "{{ docker_repo_url }}/{{ docker_apt_ansible_distribution | lower }}/gpg" +docker_apt_gpg_key_checksum: "sha256:1500c1f56fa9e26b9b8f42452a553675796ade0807cdce11975eb98170b3a570" +docker_apt_filename: "docker" + +# Used only for RedHat/CentOS/Fedora. +docker_yum_repo_url: "{{ docker_repo_url }}/{{ (ansible_distribution == 'Fedora') | ternary('fedora','centos') }}/docker-{{ docker_edition }}.repo" +docker_yum_repo_enable_nightly: '0' +docker_yum_repo_enable_test: '0' +docker_yum_gpg_key: "{{ docker_repo_url }}/centos/gpg" + +# A list of users who will be added to the docker group. +docker_users: [] + +# Docker daemon options as a dict +docker_daemon_options: {} diff --git a/ansible/roles/docker/handlers/main.yml b/ansible/roles/docker/handlers/main.yml new file mode 100644 index 0000000000..72594c8c18 --- /dev/null +++ b/ansible/roles/docker/handlers/main.yml @@ -0,0 +1,7 @@ +--- +- name: restart docker + service: + name: docker + state: "{{ docker_restart_handler_state }}" + ignore_errors: "{{ ansible_check_mode }}" + when: docker_service_manage | bool diff --git a/ansible/roles/docker/tasks/install_compose.yml b/ansible/roles/docker/tasks/install_compose.yml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ansible/roles/docker/tasks/install_docker.yml b/ansible/roles/docker/tasks/install_docker.yml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/ansible/roles/docker/tasks/main.yml b/ansible/roles/docker/tasks/main.yml new file mode 100644 index 0000000000..969874b736 --- /dev/null +++ b/ansible/roles/docker/tasks/main.yml @@ -0,0 +1,50 @@ +- name: Update apt + apt: + name: apt + state: latest + update_cache: true + +- name: Install pip + apt: + name: + - python3-pip + - python3-setuptools + state: latest + update_cache: true + +- name: Install Docker dependencies + apt: + name: + - apt-transport-https + - ca-certificates + - curl + - software-properties-common + state: present + update_cache: true + +- name: Add Docker GPG key + apt_key: + url: https://download.docker.com/linux/ubuntu/gpg + state: present + +- name: Add Docker repository + apt_repository: + repo: deb https://download.docker.com/linux/ubuntu bionic stable + filename: docker + state: present + +- name: Install Docker + apt: + name: docker-ce + state: present + update_cache: true + +- name: Upgrade pip + pip: + name: pip + state: latest + +- name: Install docker-compose + ansible.builtin.pip: + name: docker-compose + state: latest diff --git a/app_python/terraform/TF.md b/terraform/TF.md similarity index 100% rename from app_python/terraform/TF.md rename to terraform/TF.md diff --git a/app_python/terraform/docker/main.tf b/terraform/docker/main.tf similarity index 100% rename from app_python/terraform/docker/main.tf rename to terraform/docker/main.tf diff --git a/app_python/terraform/docker/outputs.tf b/terraform/docker/outputs.tf similarity index 100% rename from app_python/terraform/docker/outputs.tf rename to terraform/docker/outputs.tf diff --git a/app_python/terraform/docker/variable.tf b/terraform/docker/variable.tf similarity index 100% rename from app_python/terraform/docker/variable.tf rename to terraform/docker/variable.tf diff --git a/app_python/terraform/github/main.tf b/terraform/github/main.tf similarity index 100% rename from app_python/terraform/github/main.tf rename to terraform/github/main.tf diff --git a/app_python/terraform/github/variable.tf b/terraform/github/variable.tf similarity index 85% rename from app_python/terraform/github/variable.tf rename to terraform/github/variable.tf index 7045c0ba37..b04e46c7c3 100644 --- a/app_python/terraform/github/variable.tf +++ b/terraform/github/variable.tf @@ -1,4 +1,4 @@ -### Variables.tf ### + ### Variables.tf ### variable "token" { type = string diff --git a/app_python/terraform/yandex/main.tf b/terraform/yandex/main.tf similarity index 95% rename from app_python/terraform/yandex/main.tf rename to terraform/yandex/main.tf index 4e8eb756f5..77e1aa4dd5 100644 --- a/app_python/terraform/yandex/main.tf +++ b/terraform/yandex/main.tf @@ -16,7 +16,7 @@ resource "yandex_compute_disk" "boot-disk-1" { type = "network-hdd" zone = var.time_zone size = "20" - image_id = "fd8adntm80abl0lh2pa8" + image_id = "fd8anitv6eua45627i0e" } resource "yandex_compute_disk" "boot-disk-2" { @@ -24,7 +24,7 @@ resource "yandex_compute_disk" "boot-disk-2" { type = "network-hdd" zone = var.time_zone size = "20" - image_id = "fd8adntm80abl0lh2pa8" + image_id = "fd8anitv6eua45627i0e" } resource "yandex_compute_instance" "vm-1" { diff --git a/app_python/terraform/yandex/outputs.tf b/terraform/yandex/outputs.tf similarity index 100% rename from app_python/terraform/yandex/outputs.tf rename to terraform/yandex/outputs.tf diff --git a/app_python/terraform/yandex/variable.tf b/terraform/yandex/variable.tf similarity index 100% rename from app_python/terraform/yandex/variable.tf rename to terraform/yandex/variable.tf From d8e91b95127b3326548767f9b9cef69b812571f5 Mon Sep 17 00:00:00 2001 From: Ahmad Sarhan Date: Wed, 13 Mar 2024 04:15:09 +0300 Subject: [PATCH 05/20] deploy app_python --- ansible/ANSIBLE.md | 47 +++++++++++++++++++ ansible/inventory/yandex_cloud.yml | 2 +- ansible/playbooks/dev/main.yml | 14 ++++-- .../roles/docker/tasks/install_compose.yml | 4 ++ ansible/roles/docker/tasks/install_docker.yml | 26 ++++++++++ ansible/roles/docker/tasks/main.yml | 34 +------------- ansible/roles/web_app/README.md | 29 ++++++++++++ ansible/roles/web_app/meta/main.yml | 2 + ansible/roles/web_app/tasks/main.yml | 39 +++++++++++++++ ansible/roles/web_app/tasks/wipe.yml | 13 +++++ .../web_app/templates/docker-compose.yml.j2 | 7 +++ ansible/roles/web_app/vars/main.yml | 1 + 12 files changed, 181 insertions(+), 37 deletions(-) create mode 100644 ansible/roles/web_app/README.md create mode 100644 ansible/roles/web_app/meta/main.yml create mode 100644 ansible/roles/web_app/tasks/main.yml create mode 100644 ansible/roles/web_app/tasks/wipe.yml create mode 100644 ansible/roles/web_app/templates/docker-compose.yml.j2 create mode 100644 ansible/roles/web_app/vars/main.yml diff --git a/ansible/ANSIBLE.md b/ansible/ANSIBLE.md index 7d6d4ecdde..92b7041ef4 100644 --- a/ansible/ANSIBLE.md +++ b/ansible/ANSIBLE.md @@ -117,4 +117,51 @@ ] } } +``` + +### **`ansible-playbook playbooks/dev/main.yml -i inventory/yandex_cloud.yml`** +``` + PLAY [Install docker & deploy python app] *************************************************************************************** + + TASK [Gathering Facts] ********************************************************************************************************** + ok: [62.84.123.211] + + TASK [docker : Update apt] ****************************************************************************************************** + ok: [62.84.123.211] + + TASK [docker : Install pip] ***************************************************************************************************** + ok: [62.84.123.211] + + TASK [docker : Install Docker dependencies] ************************************************************************************* + ok: [62.84.123.211] + + TASK [docker : Add Docker GPG key] ********************************************************************************************** + ok: [62.84.123.211] + + TASK [docker : Add Docker repository] ******************************************************************************************* + ok: [62.84.123.211] + + TASK [docker : Install Docker] ************************************************************************************************** + ok: [62.84.123.211] + + TASK [docker : Install docker-compose] ****************************************************************************************** + ok: [62.84.123.211] + + TASK [web_app : create project directory] *************************************************************************************** + changed: [62.84.123.211] + + TASK [web_app : start docker] *************************************************************************************************** + ok: [62.84.123.211] + + TASK [web_app : pull the image] ************************************************************************************************* + ok: [62.84.123.211] + + TASK [web_app : create docker-compose file] ************************************************************************************* + changed: [62.84.123.211] + + TASK [web_app : run the container] ********************************************************************************************** + changed: [62.84.123.211] + + PLAY RECAP ********************************************************************************************************************** + 62.84.123.211 : ok=13 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 ``` \ No newline at end of file diff --git a/ansible/inventory/yandex_cloud.yml b/ansible/inventory/yandex_cloud.yml index 3d00f9ac3b..9898d9112c 100644 --- a/ansible/inventory/yandex_cloud.yml +++ b/ansible/inventory/yandex_cloud.yml @@ -1,4 +1,4 @@ myhost: hosts: - 51.250.102.185: + 62.84.123.211: ansible_user: ubuntu \ No newline at end of file diff --git a/ansible/playbooks/dev/main.yml b/ansible/playbooks/dev/main.yml index 7c65f4a3d5..5f1493d2e2 100644 --- a/ansible/playbooks/dev/main.yml +++ b/ansible/playbooks/dev/main.yml @@ -1,5 +1,11 @@ -- name: Run Docker role +- name: Install docker & deploy python app hosts: all - roles: - - role: docker - become: true \ No newline at end of file + become: true + vars: + folder: "/app_python" + image: "vectorsmaster/flask-app" + ex_port: "5000" + in_port: "5000" + roles: + - docker + - web_app \ No newline at end of file diff --git a/ansible/roles/docker/tasks/install_compose.yml b/ansible/roles/docker/tasks/install_compose.yml index e69de29bb2..dd3468a72c 100644 --- a/ansible/roles/docker/tasks/install_compose.yml +++ b/ansible/roles/docker/tasks/install_compose.yml @@ -0,0 +1,4 @@ +- name: Install docker-compose + ansible.builtin.pip: + name: docker-compose + state: latest diff --git a/ansible/roles/docker/tasks/install_docker.yml b/ansible/roles/docker/tasks/install_docker.yml index e69de29bb2..5ba2420cc4 100644 --- a/ansible/roles/docker/tasks/install_docker.yml +++ b/ansible/roles/docker/tasks/install_docker.yml @@ -0,0 +1,26 @@ +- name: Install Docker dependencies + apt: + name: + - apt-transport-https + - ca-certificates + - curl + - software-properties-common + state: present + update_cache: true + +- name: Add Docker GPG key + apt_key: + url: https://download.docker.com/linux/ubuntu/gpg + state: present + +- name: Add Docker repository + apt_repository: + repo: deb https://download.docker.com/linux/ubuntu bionic stable + filename: docker + state: present + +- name: Install Docker + apt: + name: docker-ce + state: present + update_cache: true diff --git a/ansible/roles/docker/tasks/main.yml b/ansible/roles/docker/tasks/main.yml index 969874b736..016872d511 100644 --- a/ansible/roles/docker/tasks/main.yml +++ b/ansible/roles/docker/tasks/main.yml @@ -12,39 +12,9 @@ state: latest update_cache: true -- name: Install Docker dependencies - apt: - name: - - apt-transport-https - - ca-certificates - - curl - - software-properties-common - state: present - update_cache: true - -- name: Add Docker GPG key - apt_key: - url: https://download.docker.com/linux/ubuntu/gpg - state: present - -- name: Add Docker repository - apt_repository: - repo: deb https://download.docker.com/linux/ubuntu bionic stable - filename: docker - state: present - name: Install Docker - apt: - name: docker-ce - state: present - update_cache: true - -- name: Upgrade pip - pip: - name: pip - state: latest + import_tasks: install_docker.yml - name: Install docker-compose - ansible.builtin.pip: - name: docker-compose - state: latest + import_tasks: install_compose.yml diff --git a/ansible/roles/web_app/README.md b/ansible/roles/web_app/README.md new file mode 100644 index 0000000000..1921fb1ed8 --- /dev/null +++ b/ansible/roles/web_app/README.md @@ -0,0 +1,29 @@ +# web_app role + +## Overview +It's an ansible role pulls vectorsmaster/flask-app image, run it, wipe docker container if stated + +## Requirements + +1. `ubuntu`. +2. `python`. +3. `docker` role. + +## Usage (navigate into ansible directory) + +1. `ansible-playbook playbooks/dev/main.yml -i inventory/yandex_cloud.yml`. + - pull the image and run it inside container. + - wipe the container. + +2. `ansible-playbook playbooks/dev/main.yml -i inventory/yandex_cloud.yml --tags "deploy"`. + - pull the image and run it inside container. + +3. `ansible-playbook playbooks/dev/main.yml -i inventory/yandex_cloud.yml --tags "wipe"`. + - wipe the container. + +## Notes + +- The container will be deployed at http://:5000 + +- You may need to upgrade ansible `pip install --upgrade ansible`. + diff --git a/ansible/roles/web_app/meta/main.yml b/ansible/roles/web_app/meta/main.yml new file mode 100644 index 0000000000..b456d40b27 --- /dev/null +++ b/ansible/roles/web_app/meta/main.yml @@ -0,0 +1,2 @@ +dependencies: + - role: docker \ No newline at end of file diff --git a/ansible/roles/web_app/tasks/main.yml b/ansible/roles/web_app/tasks/main.yml new file mode 100644 index 0000000000..4b431e1545 --- /dev/null +++ b/ansible/roles/web_app/tasks/main.yml @@ -0,0 +1,39 @@ +- name: create project directory + ansible.builtin.file: + name: "{{ folder }}/" + state: directory + tags: + - deploy + +- name: deploy python app + block: + - name: start docker + service: + name: docker + state: started + enabled: yes + + - name: pull the image + docker_image: + name: "{{ image }}" + source: pull + state: present + + - name: create docker-compose file + template: + src: docker-compose.yml.j2 + dest: "{{ folder }}/docker-compose.yml" + + - name: run the container + community.docker.docker_compose_v2: + files: + - "{{ folder }}/docker-compose.yml" + project_src: "{{ folder }}/" + tags: + - deploy + +- name: wipe python app + import_tasks: wipe.yml + when: web_app_full_wipe == true + tags: + - wipe \ No newline at end of file diff --git a/ansible/roles/web_app/tasks/wipe.yml b/ansible/roles/web_app/tasks/wipe.yml new file mode 100644 index 0000000000..d10c6c137b --- /dev/null +++ b/ansible/roles/web_app/tasks/wipe.yml @@ -0,0 +1,13 @@ +- name: Remove the container + community.docker.docker_compose_v2: + project_src: "{{ folder }}" + state: stopped + remove_images: all + remove_volumes: true + remove_orphans: true + +- name: Remove the directory + ansible.builtin.file: + path: "{{ folder }}/" + state: absent + diff --git a/ansible/roles/web_app/templates/docker-compose.yml.j2 b/ansible/roles/web_app/templates/docker-compose.yml.j2 new file mode 100644 index 0000000000..b21426c9b2 --- /dev/null +++ b/ansible/roles/web_app/templates/docker-compose.yml.j2 @@ -0,0 +1,7 @@ +version: '3' + +services: + web_app: + image: {{ image }} + ports: + - {{ in_port }}:{{ ex_port }} \ No newline at end of file diff --git a/ansible/roles/web_app/vars/main.yml b/ansible/roles/web_app/vars/main.yml new file mode 100644 index 0000000000..db89327a0b --- /dev/null +++ b/ansible/roles/web_app/vars/main.yml @@ -0,0 +1 @@ +web_app_full_wipe: true \ No newline at end of file From fb01e706d415ff5d33879c0322353e7365ddb17e Mon Sep 17 00:00:00 2001 From: Dmitriy Creed Date: Tue, 19 Mar 2024 18:01:47 +0700 Subject: [PATCH 06/20] Upload labs 9-12 Signed-off-by: Dmitriy Creed --- lab10.md | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ lab11.md | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ lab12.md | 74 +++++++++++++++++++++++++++++++++++++++++++++ lab5.md | 7 ++--- lab6.md | 2 +- lab9.md | 76 ++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 336 insertions(+), 5 deletions(-) create mode 100644 lab10.md create mode 100644 lab11.md create mode 100644 lab12.md create mode 100644 lab9.md diff --git a/lab10.md b/lab10.md new file mode 100644 index 0000000000..c472086168 --- /dev/null +++ b/lab10.md @@ -0,0 +1,91 @@ +# Lab 10: Introduction to Helm + +## Overview + +In this lab, you will become familiar with Helm, set up a local development environment, and generate manifests for your application. + +## Task 1: Helm Setup and Chart Creation + +**6 Points:** + +1. Learn About Helm: + - Begin by exploring the architecture and concepts of Helm: + - [Helm Architecture](https://helm.sh/docs/topics/architecture/) + - [Understanding Helm Charts](https://helm.sh/docs/topics/charts/) + +2. Install Helm: + - Install Helm using the instructions provided: + - [Helm Installation](https://helm.sh/docs/intro/install/) + - [Chart Repository Initialization](https://helm.sh/docs/intro/quickstart/#initialize-a-helm-chart-repository) + +3. Create Your Own Helm Chart: + - Generate a Helm chart for your application. + - Inside the `k8s` folder, create a Helm chart template by using the command `helm create your-app`. + - Replace the default repository and tag inside the `values.yaml` file with your repository name. + - Modify the `containerPort` setting in the `deployment.yml` file. + - If you encounter issues with `livenessProbe` and `readinessProbe`, you can comment them out. + + > For troubleshooting, you can use the `minikube dashboard` command. + +4. Install Your Helm Chart: + - Install your custom Helm chart and ensure that all services are healthy. Verify this by checking the `Workloads` page in the Minikube dashboard. + +5. Access Your Application: + - Confirm that your application is accessible by running the `minikube service your_service_name` command. + +6. Create a HELM.md File: + - Construct a `HELM.md` file and provide the output of the `kubectl get pods,svc` command within it. + +## Task 2: Helm Chart Hooks + +**4 Points:** + +1. Learn About Chart Hooks: + - Familiarize yourself with [Helm Chart Hooks](https://helm.sh/docs/topics/charts_hooks/). + +2. Implement Helm Chart Hooks: + - Develop pre-install and post-install pods within your Helm chart, without adding any complex logic (e.g., use "sleep 20"). You can refer to [Example 1 in the guide](https://www.golinuxcloud.com/kubernetes-helm-hooks-examples/). + +3. Troubleshoot Hooks: + - Execute the following commands to troubleshoot your hooks: + 1. `helm lint ` + 2. `helm install --dry-run helm-hooks ` + 3. `kubectl get po` + +4. Provide Output: + - Execute the following commands and include their output in your report: + 1. `kubectl get po` + 2. `kubectl describe po ` + 3. `kubectl describe po ` + +5. Hook Delete Policy: + - Implement a hook delete policy to remove the hook once it has executed successfully. + +**List of Requirements:** + +- Helm Chart with Hooks implemented, including the hook delete policy. +- Output of the `kubectl get pods,svc` command in `HELM.md`. +- Output of all commands from the step 4 of Task 2 in `HELM.md`. + +## Bonus Task: Helm Library Chart + +**To Earn 2.5 Additional Points:** + +1. Helm Chart for Extra App: + - Prepare a Helm chart for an additional application. + +2. Helm Library Charts: + - Get acquainted with [Helm Library Charts](https://helm.sh/docs/topics/library_charts/). + +3. Create a Library Chart: + - Develop a simple library chart that includes a "labels" template. You can follow the steps outlined in [the Using Library Charts guide](https://austindewey.com/2020/08/17/how-to-reduce-helm-chart-boilerplate-with-library-charts/). Use this library chart for both of your applications. + +### Guidelines + +- Ensure your documentation is clear and well-structured. +- Include all the necessary components. +- Follow appropriate file and folder naming conventions. +- Create and participate in PRs for the peer review process. +- Create pull requests (PRs) as needed: from your fork to the main branch of this repository, and from your fork's branch to your fork's master branch. + +> Note: Detailed documentation is crucial to ensure that your Helm deployment and hooks function as expected. Engage with the bonus tasks to further enhance your understanding and application deployment skills. diff --git a/lab11.md b/lab11.md new file mode 100644 index 0000000000..ba0c5efda5 --- /dev/null +++ b/lab11.md @@ -0,0 +1,91 @@ +# Lab 11: Kubernetes Secrets and Hashicorp Vault + +## Overview + +In this lab, you will learn how to manage sensitive data, such as passwords, tokens, or keys, within Kubernetes. Additionally, you will configure CPU and memory limits for your application. + +## Task 1: Kubernetes Secrets and Resource Management + +**6 Points:** + +1. Create a Secret Using `kubectl`: + - Learn about Kubernetes Secrets and create a secret using the `kubectl` command: + - [Kubernetes Secrets](https://kubernetes.io/docs/concepts/configuration/secret/) + - [Managing Secrets with kubectl](https://kubernetes.io/docs/tasks/configmap-secret/managing-secret-using-kubectl/#decoding-secret) + +2. Verify and Decode Your Secret: + - Confirm and decode the secret, then create an `11.md` file within the `k8s` folder. Provide the output of the necessary commands inside this file. + +3. Manage Secrets with Helm: + - Use Helm to manage your secrets. + - Create a `secrets.yaml` file in the `templates` folder. + - Define a `secret` object within this YAML file. + - Add an `env` field to your `Deployment`. The path to update is: `spec.template.spec.containers.env`. + + > Refer to this [Helm Secrets Video](https://www.youtube.com/watch?v=hRSlKRvYe1A) for guidance. + + - Update your Helm deployment as instructed in the video. + - Retrieve the list of pods using the command `kubectl get po`. Use the name of the pod as proof of your success within the report. + - Verify your secret inside the pod, for example: `kubectl exec demo-5f898f5f4c-2gpnd -- printenv | grep MY_PASS`. Share this output in `11.md`. + +4. Create a Pull Request: + - Generate a PR to the main branch of the forked repository. + +5. Create a Pull Request in Your Own Repository: + - Create a PR in your repository from the lab11 branch to the main one. This will facilitate the grading process. + +## Task 2: Vault Secret Management System + +**4 Points:** + +1. Install Vault Using Helm Chart: + - Install Vault using a Helm chart. Follow the steps provided in this guide: + - [Vault Installation Guide](https://developer.hashicorp.com/vault/tutorials/kubernetes/kubernetes-sidecar#install-the-vault-helm-chart) + +2. Follow the Tutorial with Your Helm Chart: + - Adapt the tutorial to work with your Helm chart, including the following steps: + - [Set a Secret in Vault](https://developer.hashicorp.com/vault/tutorials/kubernetes/kubernetes-sidecar#set-a-secret-in-vault) + - [Configure Kubernetes Authentication](https://developer.hashicorp.com/vault/tutorials/kubernetes/kubernetes-sidecar#configure-kubernetes-authentication) + - Be cautious with the service account. If you used `helm create ...`, it will be created automatically. In the guide, they create it manually. + - [Manually Define a Kubernetes Service Account](https://developer.hashicorp.com/vault/tutorials/kubernetes/kubernetes-sidecar#define-a-kubernetes-service-account) + +3. Implement Vault Secrets in Your Helm Chart: + - Use the steps from the guide as an example for your Helm chart: + - [Update values.yaml](https://developer.hashicorp.com/vault/tutorials/kubernetes/kubernetes-sidecar#launch-an-application) + - [Add Labels](https://developer.hashicorp.com/vault/tutorials/kubernetes/kubernetes-sidecar#inject-secrets-into-the-pod) + - Test to ensure your credentials are injected successfully. Use the `kubectl exec -it -- bash` command to access the container. Verify the injected secrets using `cat /path/to/your/secret` and `df -h`. Share the output in the `11.md` report. + - Apply a template as described in the guide. Test the updates as you did in the previous step and provide the outputs in `11.md`. + +**List of Requirements:** + +- Proof of work with a secret in `11.md` for the Task 1 - steps 2 and 3. +- `secrets.yaml` file. +- Resource requests and limits for CPU and memory. +- Vault configuration implemented, with proofs in `11.md`. + +## Bonus Task: Resource Management and Environment Variables + +**2.5 Points:** + +1. Read About Resource Management: + - Familiarize yourself with resource management in Kubernetes: + - [Resource Management](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) + +2. Set Up Requests and Limits for CPU and Memory for Both Helm Charts: + - Configure resource requests and limits for CPU and memory for your application. + - Test to ensure these configurations work correctly. + +3. Add Environment Variables for Your Containers for Both Helm Charts: + - Read about Kubernetes environment variables: + - [Kubernetes Environment Variables](https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/) + - Update your Helm chart with several environment variables using named templates. Move these variables to the `_helpers.tpl` file: + - [Helm Named Templates](https://helm.sh/docs/chart_template_guide/named_templates/) + +### Guidelines + +- Ensure that your documentation is clear and organized. +- Include all the necessary components. +- Follow appropriate file and folder naming conventions. +- Create pull requests (PRs) as needed: from your fork to the main branch of this repository, and from your fork's branch to your fork's master branch. + +> Note: Thorough documentation is essential to demonstrate your success in managing secrets and resource allocation in Kubernetes. Explore the bonus tasks to enhance your skills further. diff --git a/lab12.md b/lab12.md new file mode 100644 index 0000000000..79d1171972 --- /dev/null +++ b/lab12.md @@ -0,0 +1,74 @@ +# Lab 12: Kubernetes ConfigMaps + +## Overview + +In this lab, you'll delve into Kubernetes ConfigMaps, focusing on managing non-confidential data and upgrading your application for persistence. ConfigMaps provide a way to decouple configuration artifacts from image content, allowing you to manage configuration data separately from the application. + +## Task 1: Upgrade Application for Persistence + +**6 Points:** + +In this task, you'll enhance your application to persist data and explore ConfigMaps in Kubernetes. + +1. Upgrade Your Application: + - Modify your application to: + - Implement a counter logic in your application to keep track of the number of times it's accessed. + - Save the counter number in the `visits` file. + - Introduce a new endpoint `/visits` to display the recorded visits. + - Test the changes: + - Update your `docker-compose.yml` to include a new volume with your `visits` file. + - Verify that the enhancements work as expected, you must see the updated number in the `visits` file on the host machine. + - Update the `README.md` for your application. + +2. Create Pull Requests: + - Submit a PR to merge your changes into the main branch of the forked repository. + - Create a PR from the `lab12` branch to the main branch in your own repository. + +## Task 2: ConfigMap Implementation + +**4 Points:** + +1. Understand ConfigMaps: + - Read about ConfigMaps in Kubernetes: + - [ConfigMaps](https://kubernetes.io/docs/concepts/configuration/configmap/) + +2. Mount a Config File: + - Create a `files` folder with a `config.json` file. + - Populate `config.json` with data in JSON format. + - Use Helm to mount `config.json`: + - Create a `configMap` manifest, extracting data from `config.json` using `.Files.Get`. + - Update `deployment.yaml` with `Volumes` and `VolumeMounts`. + - [Example](https://carlos.mendible.com/2019/02/10/kubernetes-mount-file-pod-with-configmap/) + - Install the updated Helm chart and verify success: + - Retrieve the list of pods: `kubectl get po`. + - Use the pod name as proof of successful deployment. + - Check the ConfigMap inside the pod, e.g., `kubectl exec demo-758cc4d7c4-cxnrn -- cat /config.json`. + +3. Documentation: + - Create `12.md` in the `k8s` folder and include the output of relevant commands. + +**List of Requirements:** + +- `config.json` in the `files` folder. +- `configMap` retrieving data from `config.json` using `.Files.Get`. +- `Volume`s and `VolumeMount`s in `deployments.yml`. +- `12.md` documenting the results of commands. + +## Bonus Task: ConfigMap via Environment Variables + +**2.5 Points:** + +1. Upgrade Bonus App: + - Implement persistence logic in your bonus app. + +2. ConfigMap via Environment Variables: + - Utilize ConfigMap via environment variables in a running container using the `envFrom` property. + - Provide proof with the output of the `env` command inside your container. + +### Guidelines + +- Maintain clear and organized documentation. +- Use appropriate naming conventions for files and folders. +- For your repository PR, ensure it's from the `lab12` branch to the main branch. + +> Note: Clear documentation is crucial to demonstrate successful data persistence and ConfigMap utilization in Kubernetes. Explore the bonus tasks to further enhance your skills. diff --git a/lab5.md b/lab5.md index 7be6eee43b..a0ef405031 100644 --- a/lab5.md +++ b/lab5.md @@ -64,10 +64,9 @@ In this lab, you will get acquainted with Ansible, a powerful configuration mana 1. Create Your Custom Docker Role: - Develop a custom Ansible role for Docker with the following tasks: - 1. Install pip. - 2. Install Docker using apt and Docker Compose using pip. - 3. Update your playbook to utilize this custom role. [Tricks and Tips](https://docs.ansible.com/ansible/latest/user_guide/playbooks_best_practices.html). - 4. Test your playbook with the custom role to ensure successful deployment. + 1. Install Docker and Docker Compose. + 2. Update your playbook to utilize this custom role. [Tricks and Tips](https://docs.ansible.com/ansible/latest/user_guide/playbooks_best_practices.html). + 3. Test your playbook with the custom role to ensure successful deployment. 2. Documentation: - Develop an `ANSIBLE.md` file in the `ansible` folder to document your Ansible-related work. diff --git a/lab6.md b/lab6.md index ce6e446a6c..cffba2206e 100644 --- a/lab6.md +++ b/lab6.md @@ -69,7 +69,7 @@ In this lab, you will utilize Ansible to set up a Continuous Deployment (CD) pro 1. Create an Extra Playbook: - Develop an additional Ansible playbook specifically for your bonus application. - - Reuse the existing Ansible role you created for your primary application. + - You can reuse the existing Ansible role you created for your primary application or create a new one. - Suggested structure: ```sh diff --git a/lab9.md b/lab9.md new file mode 100644 index 0000000000..5493f042a6 --- /dev/null +++ b/lab9.md @@ -0,0 +1,76 @@ +# Lab 9: Introduction to Kubernetes + +## Overview + +In this lab, you will explore Kubernetes, set up a local development environment, and create manifests for your application. + +## Task 1: Kubernetes Setup and Basic Deployment + +**6 Points:** + +1. Learn About Kubernetes: + - Begin by studying the fundamentals of Kubernetes: + - [What is Kubernetes](https://kubernetes.io/docs/concepts/overview/what-is-kubernetes/) + - [Kubernetes Components](https://kubernetes.io/docs/concepts/overview/components/) + +2. Install Kubernetes Tools: + - Install `kubectl` and `minikube`, essential tools for managing Kubernetes. + - [Kubernetes Tools](https://kubernetes.io/docs/tasks/tools/) + +3. Deploy Your Application: + - Deploy your application within the Minikube cluster using the `kubectl create` command. Create a `Deployment` resource for your app. + - [Example of Creating a Deployment](https://kubernetes.io/docs/tutorials/hello-minikube/#create-a-deployment) + - [Deployment Overview](https://kubernetes.io/docs/tutorials/kubernetes-basics/deploy-app/deploy-intro/) + +4. Access Your Application: + - Make your application accessible from outside the Kubernetes virtual network. Achieve this by creating a `Service` resource. + - [Example of Creating a Service](https://kubernetes.io/docs/tutorials/hello-minikube/#create-a-service) + - [Service Overview](https://kubernetes.io/docs/tutorials/kubernetes-basics/expose/expose-intro/) + +5. Create a Kubernetes Folder: + - Establish a `k8s` folder within your repository. + - Create a `README.md` report within this folder and include the output of the `kubectl get pods,svc` command. + +6. Cleanup: + - Remove the `Deployment` and `Service` resources that you created, maintaining a tidy Kubernetes environment. + +## Task 2: Declarative Kubernetes Manifests + +**4 Points:** + +1. Manifest Files for Your Application: + - As a more efficient and structured approach, employ configuration files to deploy your application. + - Create a `deployment.yml` manifest file that describes your app's deployment, specifying at least 3 replicas. + - [Kubernetes Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/) + - [Declarative Management of Kubernetes Objects Using Configuration Files](https://kubernetes.io/docs/tasks/manage-kubernetes-objects/declarative-config/) + +2. Service Manifest: + - Develop a `service.yml` manifest file for your application. + +3. Manifest Files in `k8s` Folder: + - Store these manifest files in the `k8s` folder of your repository. + - Additionally, provide the output of the `kubectl get pods,svc` command in the `README.md` report. + - Include the output of the `minikube service --all` command and the result from your browser, with a screenshot demonstrating that the IP matches the output of `minikube service --all`. + +## Bonus Task: Additional Configuration and Ingress + +**To Earn 2.5 Additional Points:** + +1. Manifests for Extra App: + - Create `deployment` and `service` manifests for an additional application. + +2. Ingress Manifests: + - Construct [Ingress manifests](https://kubernetes.io/docs/tasks/access-application-cluster/ingress-minikube/) for your applications. + +3. Application Availability Check: + - Utilize `curl` or a similar tool to verify the availability of your applications. Include the output in the report. + +**Guidelines:** + +- Maintain a clear and well-structured `README.md` document. +- Ensure that all required components are included. +- Adhere to file and folder naming conventions. +- Create and participate in PRs to facilitate the peer review process. +- Create pull requests (PRs) as needed: from your fork to the main branch of this repository, and from your fork's branch to your fork's master branch. + +> Note: Detailed documentation is crucial to ensure that your Kubernetes deployment is fully functional and accessible. Engage with the bonus tasks to further enhance your understanding and application deployment skills. From e7de1545d949196925a35df841cf7542ed6e13b5 Mon Sep 17 00:00:00 2001 From: Dmitriy Creed Date: Tue, 19 Mar 2024 22:34:28 +0700 Subject: [PATCH 07/20] Lab7 fix typo Signed-off-by: Dmitriy Creed --- lab7.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lab7.md b/lab7.md index 617c232808..48e65eb202 100644 --- a/lab7.md +++ b/lab7.md @@ -28,7 +28,7 @@ In this lab, you will become familiar with a logging stack that includes Promtai ## Task 2: Documentation and Reporting -**6 Points:** +**4 Points:** 1. Logging Stack Report: - Create a new file named `LOGGING.md` to document how the logging stack you've set up functions. From af5f8984a4353f4e3038b56212890457f24d9491 Mon Sep 17 00:00:00 2001 From: Ahmad Sarhan Date: Wed, 20 Mar 2024 03:35:11 +0300 Subject: [PATCH 08/20] monitoring --- monitoring/LOGGING.md | 39 ++++++++++++++++++ monitoring/docker-compose.yml | 46 ++++++++++++++++++++++ monitoring/grafana-datasources.yml | 10 +++++ monitoring/pics/app_python_monitoring.png | Bin 0 -> 124403 bytes monitoring/pics/grafana.png | Bin 0 -> 132714 bytes monitoring/pics/loki.png | Bin 0 -> 130040 bytes monitoring/pics/promtail.png | Bin 0 -> 152473 bytes monitoring/promtail/promtail.yml | 23 +++++++++++ 8 files changed, 118 insertions(+) create mode 100644 monitoring/LOGGING.md create mode 100644 monitoring/docker-compose.yml create mode 100644 monitoring/grafana-datasources.yml create mode 100644 monitoring/pics/app_python_monitoring.png create mode 100644 monitoring/pics/grafana.png create mode 100644 monitoring/pics/loki.png create mode 100644 monitoring/pics/promtail.png create mode 100644 monitoring/promtail/promtail.yml diff --git a/monitoring/LOGGING.md b/monitoring/LOGGING.md new file mode 100644 index 0000000000..3d7d7e21f7 --- /dev/null +++ b/monitoring/LOGGING.md @@ -0,0 +1,39 @@ +# Logging Stack Report + +## Components + +1. **Promtail** : + + * **Purpose** : Promtail is a log shipper used to tail log files and send them to Loki for storage and analysis. + * **Functionality** : It collects logs from various sources, such as log files or Docker container output, and labels them with metadata before forwarding them to Loki. + * **Interaction** : Promtail interacts with Loki by sending log entries over HTTP using the Loki Push API. + +2. **Loki** : + + * **Purpose** : Loki is a horizontally scalable, highly available log aggregation system designed for cloud-native environments. + * **Functionality** : It stores logs in a manner optimized for efficient querying and retrieval, using labels for indexing and compression for storage. + * **Interaction** : Loki accepts log entries from Promtail and other clients via HTTP requests, indexes them based on labels, and stores them in object storage or a similar backend. + +3. **Grafana** : + + * **Purpose** : Grafana is a visualization and analytics platform used to visualize logs stored in Loki and create dashboards for log exploration. + * **Functionality** : It provides a user-friendly interface for querying and visualizing log data, enabling users to create custom dashboards and alerts. + * **Interaction** : Grafana connects to Loki as a data source, allowing users to query log data and display it in various formats, such as tables, graphs, and histograms. + +## results + +### web_app + +![app_python_monitoring](./pics/app_python_monitoring.png) + +### grafana + +![grafana](./pics/grafana.png) + +### loki + +![loki](./pics/loki.png) + +### promtail + +![promtail](./pics/promtail.png) diff --git a/monitoring/docker-compose.yml b/monitoring/docker-compose.yml new file mode 100644 index 0000000000..98a7a730ed --- /dev/null +++ b/monitoring/docker-compose.yml @@ -0,0 +1,46 @@ +version: '3.8' + +services: + web_app: + image: vectorsmaster/flask-app + container_name: app_python + ports: + - 5000:5000 + labels: + logging: "promtail" + + grafana: + image: grafana/grafana:latest + container_name: grafana + ports: + - 3000:3000 + volumes: + - ./grafana-datasources.yml:/etc/grafana/provisioning/datasources/datasources.yml + environment: + - GF_AUTH_ANONYMOUS_ENABLED=true + - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin + - GF_AUTH_DISABLE_LOGIN_FORM=true + labels: + logging: "promtail" + + loki: + image: grafana/loki:latest + container_name: loki + ports: + - 3100:3100 + command: -config.file=/etc/loki/local-config.yaml + labels: + logging: "promtail" + + promtail: + image: grafana/promtail:latest + container_name: promtail + volumes: + - ./promtail/promtail.yml:/etc/promtail/promtail.yml + - /var/lib/docker/containers:/var/lib/docker/containers:ro + - /var/run/docker.sock:/var/run/docker.sock + command: -config.file=/etc/promtail/promtail.yml + labels: + logging: "promtail" + depends_on: + - loki \ No newline at end of file diff --git a/monitoring/grafana-datasources.yml b/monitoring/grafana-datasources.yml new file mode 100644 index 0000000000..868c26f19e --- /dev/null +++ b/monitoring/grafana-datasources.yml @@ -0,0 +1,10 @@ +apiVersion: 1 + +datasources: + - name: Loki + type: loki + access: proxy + url: http://loki:3100 + version: 1 + editable: false + isDefault: true \ No newline at end of file diff --git a/monitoring/pics/app_python_monitoring.png b/monitoring/pics/app_python_monitoring.png new file mode 100644 index 0000000000000000000000000000000000000000..e5ede315070d224c9a68f8ae5dd11151911a7d9a GIT binary patch literal 124403 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