diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 000000000..37e334c1c --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1 @@ +# Workflow Test Сб 21 мар 2026 00:12:21 MSK diff --git a/.github/workflows/terraform-apply.yml b/.github/workflows/terraform-apply.yml new file mode 100644 index 000000000..fe71a21e7 --- /dev/null +++ b/.github/workflows/terraform-apply.yml @@ -0,0 +1,65 @@ +# ============================================================ +# GitHub Actions: Terraform Apply на merge в main +# Файл: .github/workflows/terraform-apply.yml +# ============================================================ + +name: Terraform Apply + +on: + push: + branches: [ main ] + +jobs: + terraform-apply: + name: 'Terraform Apply' + runs-on: ubuntu-latest + environment: production + defaults: + run: + shell: bash + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: '1.6.0' + + - name: Terraform Init + run: | + cd bootstrap + terraform init \ + -backend-config="bucket=${{ secrets.TF_BUCKET_NAME }}" \ + -backend-config="access_key=${{ secrets.TF_ACCESS_KEY }}" \ + -backend-config="secret_key=${{ secrets.TF_SECRET_KEY }}" \ + -reconfigure + + - name: Terraform Validate + run: | + cd bootstrap + terraform validate + + - name: Terraform Plan + run: | + cd bootstrap + terraform plan -no-color -out=tfplan + env: + TF_VAR_yandex_token: ${{ secrets.YC_TOKEN }} + YC_TOKEN: ${{ secrets.YC_TOKEN }} + + - name: Terraform Apply + run: | + cd bootstrap + terraform apply -auto-approve tfplan + env: + TF_VAR_yandex_token: ${{ secrets.YC_TOKEN }} + YC_TOKEN: ${{ secrets.YC_TOKEN }} + + - name: Update Outputs + run: | + cd bootstrap + terraform output -json > ../infrastructure/bootstrap-outputs.json + env: + YC_TOKEN: ${{ secrets.YC_TOKEN }} diff --git a/.github/workflows/terraform-plan.yml b/.github/workflows/terraform-plan.yml new file mode 100644 index 000000000..5f151627e --- /dev/null +++ b/.github/workflows/terraform-plan.yml @@ -0,0 +1,62 @@ +# ============================================================ +# GitHub Actions: Terraform Plan на Pull Request +# Файл: .github/workflows/terraform-plan.yml +# ============================================================ + +name: Terraform Plan + +on: + pull_request: + branches: [ main ] + + +jobs: + terraform-plan: + name: 'Terraform Plan' + runs-on: ubuntu-latest + defaults: + run: + shell: bash + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Terraform + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: '1.6.0' + + - name: Terraform Init + run: | + cd bootstrap + terraform init \ + -backend-config="bucket=${{ secrets.TF_BUCKET_NAME }}" \ + -backend-config="access_key=${{ secrets.TF_ACCESS_KEY }}" \ + -backend-config="secret_key=${{ secrets.TF_SECRET_KEY }}" + + - name: Terraform Validate + run: | + cd bootstrap + terraform validate + + - name: Terraform Plan + run: | + cd bootstrap + terraform plan -no-color -out=tfplan + env: + TF_VAR_yandex_token: ${{ secrets.YC_TOKEN }} + YC_TOKEN: ${{ secrets.YC_TOKEN }} + + - name: Comment PR with Plan Output + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const plan = fs.readFileSync('bootstrap/tfplan', 'utf8'); + await github.rest.issues.createComment({ + issue_number: context.payload.pull_request.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: `## 🗂️ Terraform Plan Output\n\n\`\`\`\n${plan.substring(0, 65000)}\n\`\`\`` + }); diff --git a/.gitignore b/.gitignore index 3a084f2c1..799d4918e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,122 +1,43 @@ -.lock.hcl -.tfstate -.vagrant -*.retry -**/vagrant_ansible_inventory -*.iml -temp -contrib/offline/offline-files -contrib/offline/offline-files.tar.gz -.idea -.vscode -.tox -.cache -*.bak +# ============================================================ +# TERRAFORM - Чувствительные файлы (В НАЧАЛЕ!) +# ============================================================ +*.tfplan +*.tfplan.json *.tfstate -*.tfstate*backup -*.lock.hcl +*.tfstate.backup +*.tfstate.* +*.tfvars .terraform/ -contrib/terraform/aws/credentials.tfvars .terraform.lock.hcl -/ssh-bastion.conf -**/*.sw[pon] -*~ -vagrant/ -plugins/mitogen - -# Ansible inventory -inventory/* -!inventory/local -!inventory/sample -inventory/*/artifacts/ - -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# Distribution / packaging -.Python -env/ -build/ -credentials/ -develop-eggs/ -dist/ -downloads/ -eggs/ -.eggs/ -parts/ -sdist/ -var/ -*.egg-info/ -.installed.cfg -*.egg - -# PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. -*.manifest -*.spec - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.coverage -.coverage.* -.cache -nosetests.xml -coverage.xml -*,cover -.hypothesis/ - -# Translations -*.mo -*.pot - -# Django stuff: +!*.tfvars.example +infrastructure/tfplan +infrastructure/tfplan.* +bootstrap/tfplan +bootstrap/tfplan.* +kubernetes-kubeadm/tfplan +kubernetes-kubeadm/tfplan.* + +# ============================================================ +# OUTPUTS С СЕКРЕТАМИ +# ============================================================ +*-outputs.json +bootstrap-outputs.json +infra-outputs.json + +# ============================================================ +# СЕКРЕТЫ И КЛЮЧИ +# ============================================================ +*.pem +*.key +*.secret +credentials.json +*.password +*.env + +# ============================================================ +# ЛОКАЛЬНЫЕ КОНФИГИ +# ============================================================ +.kube/ *.log -local_settings.py - -# Flask stuff: -instance/ -.webassets-cache - -# Scrapy stuff: -.scrapy - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - -# IPython Notebook -.ipynb_checkpoints - -# pyenv -.python-version - -# dotenv -.env - -# virtualenv -venv/ -ENV/ - -# molecule -roles/**/molecule/**/__pycache__/ - -# macOS .DS_Store - -# Temp location used by our scripts -scripts/tmp/ -tmp.md - -# Ansible collection files -kubernetes_sigs-kubespray*tar.gz -ansible_collections +*~ diff --git a/README.md b/README.md index 59a42389c..44d68a3b9 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Дипломный практикум в Yandex.Cloud +# Дипломный практикум в Yandex.Cloud - Барышков Михаил * [Цели:](#цели) * [Этапы выполнения:](#этапы-выполнения) * [Создание облачной инфраструктуры](#создание-облачной-инфраструктуры) @@ -21,6 +21,58 @@ 5. Настроить CI для автоматической сборки и тестирования. 6. Настроить CD для автоматического развёртывания приложения. +--- + +# ЭТАП 0: ПОДГОТОВКА ИНФРАСТРУКТУРЫ + +## 1.1. Архитектура инфраструктуры + +```text +┌─────────────────────────────────────────────────────────────┐ +│ Yandex Cloud │ +│ ┌─────────────────────────────────────────────────────┐ │ +│ │ VPC: diploma-dev-network │ │ +│ │ CIDR: 10.0.0.0/16 │ │ +│ │ │ │ +│ │ ┌─────────────────────────────────────────────┐ │ │ +│ │ │ Subnet: diploma-dev-subnet-a │ │ │ +│ │ │ Zone: ru-central1-a │ │ │ +│ │ │ CIDR: 10.0.0.0/24 │ │ │ +│ │ └─────────────────────────────────────────────┘ │ │ +│ │ │ │ +│ │ Security Group: diploma-dev-k8s-nodes-sg │ │ +│ │ - Allow: 22, 6443, 80, 443, 30000-32767 │ │ +│ │ │ │ +│ └─────────────────────────────────────────────────────┘ │ +│ │ +│ S3 Bucket: diploma-tfstate-bucket │ +│ - Terraform state storage │ +│ - KMS encryption enabled │ +└─────────────────────────────────────────────────────────────┘ +``` + +## 1.2. Структура проекта + +```text + +~/git/homework/devops-diplom-yandexcloud/ +├── bootstrap/ # Инициализация backend и сервисных аккаунтов +│ ├── main.tf +│ ├── variables.tf +│ ├── outputs.tf +│ └── providers.tf +├── infrastructure/ # Сетевая инфраструктура +│ ├── main.tf # VPC, подсети, security groups +│ ├── variables.tf +│ ├── outputs.tf +│ └── providers.tf +├── .github/workflows/ # GitHub Actions workflows +│ ├── terraform-apply.yml +│ └── terraform-plan.yml +└── README.md + +``` + --- ## Этапы выполнения: @@ -50,8 +102,197 @@ 1. Terraform сконфигурирован и создание инфраструктуры посредством Terraform возможно без дополнительных ручных действий, стейт основной конфигурации сохраняется в бакете или Terraform Cloud 2. Полученная конфигурация инфраструктуры является предварительной, поэтому в ходе дальнейшего выполнения задания возможны изменения. +## Решение : + +### Раздел: Проектирование облачной инфраструктуры +Для автоматизации развёртывания инфраструктуры в облаке Yandex.Cloud использовался инструмент Infrastructure as Code — Terraform. Архитектура конфигураций разделена на два логических этапа: + +1. **Bootstrap-этап**: создание сервисного аккаунта с минимально необходимыми правами (роль `editor`) и bucket в Object Storage для хранения state-файла. Такой подход соответствует принципу наименьших привилегий: сервисный аккаунт не имеет прав суперпользователя, что снижает риски при компрометации учетных данных. +2. **Infrastructure-этап**: развёртывание сетевой инфраструктуры — VPC с подсетями в трёх зонах доступности (`ru-central1-a`, `ru-central1-b`, `ru-central1-d`) для обеспечения отказоустойчивости. Все подсети настроены с включённым NAT для обеспечения исходящего доступа к интернету, необходимого для загрузки контейнерных образов и обновлений. + +Для хранения состояния Terraform использовался remote backend на базе S3-совместимого хранилища Yandex Object Storage с включённым шифрованием AES256. Это позволяет обеспечить целостность state-файла и возможность командной работы с инфраструктурой. +Все чувствительные данные (токены, ключи доступа) передаются через переменные окружения или защищённые файлы `terraform.tfvars`, исключённые из системы контроля версий через `.gitignore`. Это соответствует лучшим практикам безопасности при работе с облачными провайдерами. +Особое внимание уделено оптимизации затрат: в конфигурацию заложена возможность использования прерываемых виртуальных машин (preemptible instances) для worker-узлов будущего Kubernetes-кластера, что позволяет сократить расходы на вычислительные ресурсы до 80% без потери функциональности в тестовом окружении. + +### Раздел: Реализация безопасного хранения состояния Terraform +Для обеспечения целостности и конфиденциальности state-файла инфраструктуры был использован remote backend на базе Yandex Object Storage с обязательным шифрованием. Конфигурация основана на проверенных паттернах из предыдущих этапов работы: + +1. **KMS-шифрование:** Создан симметричный ключ `yandex_kms_symmetric_key` с алгоритмом AES-128 и периодом ротации 1 год. Ключ используется в блоке `server_side_encryption_configuration` ресурса бакета с параметром `sse_algorithm = "aws:kms"`, что является единственным поддерживаемым значением в провайдере Yandex Cloud. +2. **Организация ресурсов:** Всем ресурсам явно указан `folder_id`, что обеспечивает корректный учёт затрат в рамках папки проекта и соответствие требованиям организационной структуры облака. +3. **Минимальные привилегии:** Сервисный аккаунт `terraform-sa` имеет только роль `editor` на уровне папки, без прав администратора каталога, что снижает поверхность атаки при компрометации учетных данных. +4. **Управление жизненным циклом:** На бакете настроена политика автоматического удаления старых версий объектов через 30 дней, что предотвращает неконтролируемый рост затрат на хранение. + +Предупреждение о депрекации атрибута `acl` принято осознанно: в учебном проекте приоритетом является работоспособность конфигурации, а миграция на `yandex_storage_bucket_grant` может быть выполнена на этапе промышленной эксплуатации. + --- -### Создание Kubernetes кластера +### 📦 ЭТАП 1.1: Bootstrap — Сервисный аккаунт и S3 Backend + +### [bootstrap/providers.tf](bootstrap/providers.tf) +### [bootstrap/variables.tf](bootstrap/variables.tf) +### [bootstrap/main.tf](bootstrap/main.tf) +### [bootstrap/outputs.tf](bootstrap/outputs.tf) + +### 🔄 Выполнение этапа Bootstrap + +### 📋 Пошаговая инструкция: + +```bash +# 1. Перейдите в директорию bootstrap +cd bootstrap + +# 2. Инициализация Terraform (state будет локальным) +terraform init + +# 3. Проверка конфигурации +terraform validate +terraform fmt -check +``` + +![img1](bootstrap/img/img1.png) + +``` bash +# 4. Просмотр плана изменений +terraform plan -out=tfplan +``` + +![img2](bootstrap/img/img2.png) + +```bash +# 5. Применение конфигурации +terraform apply tfplan + +# 6. Сохраните выходные значения для следующего этапа +terraform output -json > ../infrastructure/bootstrap-outputs.json +``` + +![img3](bootstrap/img/img3.png) + +### Консоль KMS → Созданный ключ шифрования + +![img4-1](bootstrap/img/img4-1.png) + +### Консоль KMS → Созданный сервисный аккаунт + +![img5](bootstrap/img/img5.png) + +### Консоль KMS → Созданный бакет + +![img4](bootstrap/img/img4.png) +![img6](bootstrap/img/img6.png) +![img7](bootstrap/img/img7.png) + + +### 🏗️ ЭТАП 1.2: Infrastructure — VPC и сетевая инфраструктура + +Теперь создаём основную инфраструктуру, используя bucket из bootstrap как backend. + +### 🗂️ Структура папки infrastructure/ + +```text +infrastructure/ +├── main.tf # VPC, подсети, security groups +├── variables.tf # Переменные конфигурации +├── outputs.tf # Выходные значения +├── providers.tf # Провайдер + backend конфигурация +├── terraform.tfvars # Шаблон переменных +└── .gitignore # Исключаем секреты +``` + +### [infrastructure/providers.tf](infrastructure/providers.tf) +### [infrastructure/variables.tf](infrastructure/variables.tf) +### [infrastructure/main.tf](infrastructure/main.tf) +### [infrastructure/outputs.tf](infrastructure/outputs.tf) + +### Раздел: Результаты развёртывания сетевой инфраструктуры +После успешной инициализации backend и валидации конфигурации была выполнена команда terraform apply, в результате которой в облаке Яндекс.Облако созданы следующие ресурсы: + +1. VPC Network diploma-dev-network с CIDR-блоком `10.0.0.0/16` — изолированная сетевая среда проекта. +2. Три подсети в зонах доступности `ru-central1-a`, `ru-central1-b`, `ru-central1-d` с включённым NAT для обеспечения исходящего доступа к интернету. Такое распределение обеспечивает отказоустойчивость будущего Kubernetes-кластера: при недоступности одной зоны мастер-ноды и рабочие узлы продолжат работу в остальных зонах. +3. Группа безопасности diploma-dev-k8s-nodes-sg с правилами межсетевого экрана, разрешающими: + - Порт 22 (SSH) — для административного доступа к узлам; + - Порты 80/443 (HTTP/HTTPS) — для входящего трафика приложений; + - Диапазон 30000-32767 (NodePort) — для сервисов Kubernetes типа NodePort; + - Внутренний трафик в пределах CIDR сети — для коммуникации компонентов кластера. + +Состояние инфраструктуры (terraform state) автоматически сохраняется в защищённый bucket Object Storage с шифрованием через KMS, что обеспечивает целостность данных и возможность командной работы. Все чувствительные параметры передаются через CLI-аргументы и не хранятся в репозитории, что соответствует лучшим практикам безопасности. + +Вместо устаревшего параметра nat = true использована современная архитектура маршрутизации через yandex_vpc_gateway с типом shared_egress_gateway. Это соответствует рекомендациям Яндекс.Облако для организации исходящего доступа из приватных подсетей. + + +### 🔄 Пошаговая инструкция запуска + +```bash +# 1. Перейдите в директорию infrastructure +cd ~/git/homework/devops-diplom-yandexcloud/infrastructure + + +# 2. Инициализация с настройкой backend +# Извлеките значения из bootstrap-outputs.json (только для локального использования!) +export TF_BACKEND_ACCESS_KEY=$(jq -r '.backend_access_key.value' bootstrap-outputs.json) +export TF_BACKEND_SECRET_KEY=$(jq -r '.backend_secret_key.value' bootstrap-outputs.json) +export TF_BUCKET_NAME=$(jq -r '.bucket_name.value' bootstrap-outputs.json) + +# Проверьте, что переменные установлены: +echo "Bucket: $TF_BUCKET_NAME" +echo "Access Key: ${TF_BACKEND_ACCESS_KEY:0:10}..." # Покажет только первые 10 символов + +terraform init \ + -backend-config="bucket=${TF_BUCKET_NAME}" \ + -backend-config="access_key=${TF_BACKEND_ACCESS_KEY}" \ + -backend-config="secret_key=${TF_BACKEND_SECRET_KEY}" +``` + +![img0](infrastructure/img/img0.png) + +```bash +# 5. Проверка конфигурации + + +terraform validate +``` + +![img1](infrastructure/img/img1.png) + +```bash +terraform plan -out=tfplan +``` + +![img2](infrastructure/img/img2.png) + +```bash +# 6. Применение инфраструктуры + +terraform apply tfplan +``` + +![img3](infrastructure/img/img3.png) + +### Консоль → VPC → Сети → diploma-dev-network + + +![img4](infrastructure/img/img4.png) + +### Консоль → VPC → Группы безопасности → diploma-dev-k8s-nodes-sg + +![img5](infrastructure/img/img5.png) +![img6](infrastructure/img/img6.png) + + +### Консоль → Object Storage → diploma-tfstate-bucket-unique → terraform/infrastructure.tfstate + +![img7](infrastructure/img/img7.png) + +### 1.5. Результаты этапа + +- ✅ Создан S3 bucket для хранения Terraform state +- ✅ Настроен сервисный аккаунт с правами editor +- ✅ Создана VPC сеть с подсетью в зоне `ru-central1-a`, `ru-central1-b`, `ru-central1-d` +- ✅ Настроена Security Group с необходимыми правилами +- ✅ Настроен GitHub Actions pipeline для автоматического применения инфраструктуры + + +--- +## Создание Kubernetes кластера На этом этапе необходимо создать [Kubernetes](https://kubernetes.io/ru/docs/concepts/overview/what-is-kubernetes/) кластер на базе предварительно созданной инфраструктуры. Требуется обеспечить доступ к ресурсам из Интернета. @@ -72,7 +313,323 @@ 3. Команда `kubectl get pods --all-namespaces` отрабатывает без ошибок. --- -### Создание тестового приложения + +# ЭТАП 2: СОЗДАНИЕ KUBERNETES КЛАСТЕРА + + +## Решение + +## 2.1. Обоснование выбора подхода к развёртыванию Kubernetes + +В ходе работы над проектом было принято решение использовать подход с самоуправляемым +развёртыванием Kubernetes кластера посредством инструмента `kubeadm` вместо Managed +Kubernetes сервиса Яндекс.Облако. Данное решение обусловлено следующими факторами: + +1. **Обход ограничений прав доступа**: В учебном окружении с ограниченным бюджетом + купона создание Managed Kubernetes кластеров через API требовало дополнительных + прав (`k8s.admin` на уровне организации), которые не были предоставлены. Подход + с самоуправляемыми ВМ позволяет развернуть кластер с минимальными правами + (`editor` на уровне папки). + +2. **Глубокое понимание архитектуры**: Ручная настройка компонентов Kubernetes + (etcd, kube-apiserver, containerd, CNI-плагин) демонстрирует понимание внутренних + механизмов работы оркестратора, что является важным образовательным результатом + дипломного проекта. + +3. **Гибкость конфигурации**: Подход через `kubeadm` позволяет точно контролировать + версии компонентов, параметры сети, политики безопасности и интеграцию с внешними + системами, что важно для воспроизводимости развёртывания. + +4. **Экономия ресурсов**: Использование прерываемых виртуальных машин для рабочих + нод и минимальных пресетов ресурсов (2 vCPU, 2 GB RAM) позволяет уложиться в + ограниченный бюджет купона при сохранении функциональности кластера. + + +## 2.2. Архитектура развёртывания кластера + +Кластер развёрнут в следующей конфигурации: + +| Компонент | Конфигурация | Обоснование | +|-----------|-------------|-------------| +| Мастер-нода | 2 vCPU, 4 GB RAM, network-hdd, non-preemptible | Стабильность управления кластером | +| Рабочие ноды | 2 vCPU, 2 GB RAM, network-hdd, preemptible | Экономия до 80% стоимости | +| Операционная система | Ubuntu 20.04 LTS | Долгосрочная поддержка, совместимость | +| CRI (Container Runtime) | containerd | Стандарт de-facto, легковесный | +| CNI (сетевой плагин) | Calico | Поддержка NetworkPolicy, хорошая производительность | +| Версия Kubernetes | 1.28 (LTS) | Стабильность и долгосрочная поддержка | +| CIDR для подов | 10.244.0.0/16 | Не пересекается с CIDR VPC (10.0.0.0/16) | + +### Схема инфраструктуры + +```text +┌─────────────────────────────────────────────────────────────┐ +│ Kubernetes Cluster (kubeadm) │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ ┌──────────────────────────────────────────────────┐ │ +│ │ Master Node (diploma-k8s-master) │ │ +│ │ IP: 10.0.0.19 / 89.169.133.106 │ │ +│ │ Resources: 2 vCPU, 4 GB RAM │ │ +│ │ Components: │ │ +│ │ - etcd │ │ +│ │ - kube-apiserver │ │ +│ │ - kube-controller-manager │ │ +│ │ - kube-scheduler │ │ +│ └──────────────────────────────────────────────────┘ │ +│ │ +│ ┌──────────────────────────────────────────────────┐ │ +│ │ Worker Node 1 (diploma-k8s-worker-1) │ │ +│ │ IP: 10.0.0.28 / 46.21.246.4 │ │ +│ │ Resources: 2 vCPU, 2 GB RAM (preemptible) │ │ +│ │ Components: │ │ +│ │ - kubelet │ │ +│ │ - kube-proxy │ │ +│ │ - containerd │ │ +│ └──────────────────────────────────────────────────┘ │ +│ │ +│ ┌──────────────────────────────────────────────────┐ │ +│ │ Worker Node 2 (diploma-k8s-worker-2) │ │ +│ │ IP: 10.0.0.36 / 46.21.246.204 │ │ +│ │ Resources: 2 vCPU, 2 GB RAM (preemptible) │ │ +│ │ Components: │ │ +│ │ - kubelet │ │ +│ │ - kube-proxy │ │ +│ │ - containerd │ │ +│ └──────────────────────────────────────────────────┘ │ +│ │ +│ CNI: Calico (10.244.0.0/16) │ +│ Service CIDR: 10.96.0.0/12 │ +└─────────────────────────────────────────────────────────────┘ +``` +### Структура файлов +```text +kubernetes-kubeadm/ +├── main.tf # Провижининг ВМ через Terraform +├── variables.tf +├── outputs.tf +├── providers.tf +├── ansible/ +│ ├── hosts.ini # Ansible inventory +│ ├── install-node.sh # Скрипт установки Kubernetes +│ └── init-cluster.sh # Скрипт инициализации кластера +└── terraform.tfvars +``` + +### Примечание по архитектуре кластера + +В рамках дипломного проекта кластер развёрнут в single-zone конфигурации +(все ноды в зоне доступности ru-central1-a). Данный выбор обусловлен: + +1. **Ограниченным бюджетом** образовательного купона — размещение в одной зоне + снижает расходы на NAT Gateway и исходящий трафик. + +2. **Учебным характером проекта** — для демонстрации функциональности + Kubernetes и установки системы мониторинга отказоустойчивость по зонам + не является критическим требованием. + +3. **Ограниченным временем** выполнения проекта — single-zone конфигурация + позволяет быстрее завершить развёртывание и перейти к следующим этапам. + +Для **промышленной эксплуатации** рекомендуется использовать multi-zone +архитектуру с размещением нод в 3 зонах доступности (ru-central1-a, +ru-central1-b, ru-central1-d) для обеспечения высокой доступности и +отказоустойчивости кластера. + +## Раздел 2.3: Процесс развёртывания + +Развёртывание выполнено в несколько этапов: + +## Этап 2.3.1: Провижининг инфраструктуры через Terraform + +Создание виртуальных машин выполнено посредством Terraform с использованием +конфигурации, совместимой с ранее развёрнутой сетевой инфраструктурой: + +```bash +# Инициализация с remote backend (S3 + KMS) +terraform init \ + -backend-config="bucket=${TF_BUCKET_NAME}" \ + -backend-config="access_key=${TF_BACKEND_ACCESS_KEY}" \ + -backend-config="secret_key=${TF_BACKEND_SECRET_KEY}" + +# Проверка конфигурации +terraform validate + +# Применение конфигурации +terraform apply -auto-approve +``` + +В результате создано 3 виртуальные машины в зоне доступности ru-central1-a: + + - 1 мастер-нода с фиксированным публичным IP для доступа к API + - 2 рабочие ноды с прерываемым типом инстансов для экономии средств + +![img1](kubernetes-kubeadm/img/img1.png) +![img2](kubernetes-kubeadm/img/img2.png) + +### Этап 2.3.2: Установка компонентов Kubernetes на все ноды +На каждой ноде (мастер + рабочие) выполнен скрипт установки, который: + +1. Настраивает модули ядра (overlay, br_netfilter) для работы сетевых плагинов +2. Конфигурирует параметры сети (net.bridge.bridge-nf-call-iptables, ip_forward) +3. Устанавливает и настраивает containerd с поддержкой systemd cgroup +4. Добавляет официальный репозиторий Kubernetes и устанавливает пакеты +5. kubeadm, kubelet, kubectl версии 1.28 +6. Отключает swap (требуется для работы kubelet) +7. Запускает и включает сервис kubelet + +### Этап 2.3.3: Инициализация кластера и присоединение рабочих нод +На мастер-ноде выполнена инициализация кластера: + +```bash +sudo kubeadm init \ + --control-plane-endpoint=10.0.0.19 \ + --pod-network-cidr=10.244.0.0/16 \ + --ignore-preflight-errors=NumCPU,Mem +``` + +После успешной инициализации: + + Настроен доступ kubectl для текущего пользователя + Установлен сетевой плагин Calico для обеспечения связности подов + Сгенерирована команда kubeadm join для присоединения рабочих нод + +Рабочие ноды присоединены к кластеру выполнением команды join с токеном и хэшем сертификата CA. + +### Этап 2.3.4: Проверка работоспособности кластера + +После присоединения всех нод выполнена проверка: + +- Все узлы отображаются в статусе Ready через kubectl get nodes +- Системные поды в пространстве имён kube-system в статусе Running +- Сетевой плагин Calico обеспечивает маршрутизацию между нодами + +### 2.4. Результаты этапа +- ✅ Создано 3 ВМ (1 master + 2 workers) через Terraform +- ✅ Установлен Kubernetes 1.28 через kubeadm +- ✅ Настроен containerd как CRI +- ✅ Развёрнут Calico CNI для сетевой связности +- ✅ Все ноды в статусе Ready +- ✅ Использованы прерываемые ВМ для экономии (до 80% дешевле) + +--- + +# 🔧 Пошаговые команды для настройки нод + +## 📦 Шаг 0: Подготовьте скрипты на локальной машине + +### Создайте [install-node.sh](kubernetes-kubeadm/ansible/install-node.sh) (универсальный скрипт для всех нод) + +### Создайте [init-cluster.sh](kubernetes-kubeadm/ansible/init-cluster.sh) (только для мастера) + +## На мастер-ноде (89.169.133.106): + +```bash +# Подключитесь к мастеру +ssh yc-user@89.169.133.106 + +# Скопируйте скрипт установки (вставьте содержимое install-node.sh) +# Или передайте файл: +# На локальной машине: +scp ansible/install-node.sh yc-user@89.169.133.106:~/ + +# На мастере выполните: +bash ~/install-node.sh +``` + +![img3-1](kubernetes-kubeadm/img/img3-1.png) +![img3-2](kubernetes-kubeadm/img/img3-2.png) +![img3-3](kubernetes-kubeadm/img/img3-3.png) +![img3-4](kubernetes-kubeadm/img/img3-4.png) + +## На worker-1 (46.21.246.4) + +```bash +# Подключитесь к worker-1 +ssh yc-user@46.21.246.4 + +# Скопируйте и выполните тот же скрипт +bash ~/install-node.sh +# Или: scp с локальной машины, затем bash +``` + +![img4-1](kubernetes-kubeadm/img/img4-1.png) +![img4-2](kubernetes-kubeadm/img/img4-2.png) +![img4-3](kubernetes-kubeadm/img/img4-3.png) +![img4-4](kubernetes-kubeadm/img/img4-4.png) + +## На worker-2 (46.21.246.204): + +```bash +# Подключитесь к worker-2 +ssh yc-user@46.21.246.204 + +# Скопируйте и выполните тот же скрипт +bash ~/install-node.sh +``` + +![img5-1](kubernetes-kubeadm/img/img5-1.png) +![img5-2](kubernetes-kubeadm/img/img5-2.png) +![img5-3](kubernetes-kubeadm/img/img5-3.png) +![img5-4](kubernetes-kubeadm/img/img5-4.png) + +## 🎯 Шаг 2: Инициализируйте кластер на мастере + +```bash +# Убедитесь, что вы на мастере (89.169.133.106) +# Если нет — подключитесь: +ssh yc-user@89.169.133.106 + +# Скопируйте скрипт инициализации +# На локальной машине: +scp ansible/init-cluster.sh yc-user@89.169.133.106:~/ + +# На мастере выполните: +bash ~/init-cluster.sh +``` +![img6-1](kubernetes-kubeadm/img/img6-1.png) +![img6-2](kubernetes-kubeadm/img/img6-2.png) +![img6-3](kubernetes-kubeadm/img/img6-3.png) +![img6-4](kubernetes-kubeadm/img/img6-4.png) + +## Шаг 3: Проверьте кластер на мастере + +```bash +# Вернитесь на мастер +ssh yc-user@89.169.133.106 + +# 1. Проверьте ноды (подождите 1-2 минуты после join) +kubectl get nodes -o wide +``` +![img7](kubernetes-kubeadm/img/img7.png) + +```bash +# 2. Проверьте системные поды +kubectl get pods -n kube-system -o +# ✅ Должны быть: coredns, calico-node, kube-proxy в статусе +``` + +![img8](kubernetes-kubeadm/img/img8.png) + +```bash +# 3. Проверьте подключение к API +kubectl cluster-info +``` + +![img9](kubernetes-kubeadm/img/img9.png) + +```bash +# 4. Проверьте версию +kubectl version --output=yaml +``` + +![img10](kubernetes-kubeadm/img/img10.png) + +## Сетевая инфраструктура для размещения кластера + +![img11](kubernetes-kubeadm/img/img11.png) + + +## Создание тестового приложения Для перехода к следующему этапу необходимо подготовить тестовое приложение, эмулирующее основное приложение разрабатываемое вашей компанией. @@ -90,8 +647,137 @@ 2. Регистри с собранным docker image. В качестве регистри может быть DockerHub или [Yandex Container Registry](https://cloud.yandex.ru/services/container-registry), созданный также с помощью terraform. --- -### Подготовка cистемы мониторинга и деплой приложения +# ЭТАП 3: ТЕСТОВОЕ ПРИЛОЖЕНИЕ И РЕЕСТР +## Решение + +## 3.1. Архитектура приложения + +```text +┌────────────────────────────────────────────────┐ +│ Diploma Test Application │ +├────────────────────────────────────────────────┤ +│ │ +│ 📦 Docker Image │ +│ Base: nginx:1.25-alpine │ +│ Size: ~28 MB │ +│ │ +│ 📄 Files: │ +│ - Dockerfile │ +│ - nginx.conf (с health checks) │ +│ - index.html (статическая страница) │ +│ │ +│ 🌐 Endpoints: │ +│ - / → Главная страница │ +│ - /health → Liveness probe │ +│ - /ready → Readiness probe │ +│ - /api/hostname → Имя пода │ +│ │ +│ 🗄️ Registry: │ +│ DockerHub: nastya2005/diploma-test-app │ +│ │ +└────────────────────────────────────────────────┘ +``` + +### 🗂️ Структура нового репозитория +Создадим отдельный репозиторий для приложения (не в основном проекте): +```test +~/git/diploma-test-app/ +├── Dockerfile # Инструкция сборки +├── nginx.conf # Конфигурация nginx +├── index.html # Статическая страница +├── .dockerignore +├── .gitignore +├── .github/workflows/ +│ └── ci-cd.yml # CI/CD pipeline +└── README.md +``` + +## 📄 Шаг 1: Создайте репозиторий и файлы +### 1.1. Создайте папку и инициализируйте git + +```bash +# Создайте папку для приложения +mkdir -p ~/git/diploma-test-app +cd ~/git/diploma-test-app + +# Инициализируйте git +git init + +# Создайте .gitignore +``` +[.gitignore](https://github.com/BaryshkovMikhail/diploma-test-app/blob/main/.gitignore) + +### 1.2. Создайте index.html (статическая страница) + +[index.html](https://github.com/BaryshkovMikhail/diploma-test-app/blob/main/index.html) + +### 1.3. Создайте nginx.conf + +[nginx.conf](https://github.com/BaryshkovMikhail/diploma-test-app/blob/main/nginx.conf) + +### 1.4. Создайте Dockerfile + +[Dockerfile](https://github.com/BaryshkovMikhail/diploma-test-app/blob/main/Dockerfile) + +### 1.5. Создайте README.md + +[README.md](https://github.com/BaryshkovMikhail/diploma-test-app/blob/main/README.md) + +### 1.6 Созадение и запуск прилоложения + +![img12](kubernetes-kubeadm/img/img12.png) +![img13](kubernetes-kubeadm/img/img13.png) +![img14](kubernetes-kubeadm/img/img14.png) + +## Решение 1: Использовать публичный DockerHub +```bash +# На локальной машине +cd ~/git/diploma-test-app + +# Ваш DockerHub username +DOCKERHUB_USER="nastya2005" + +# Сформируйте имя образа для DockerHub +IMAGE_NAME="${DOCKERHUB_USER}/diploma-test-app:latest" +echo "Building for DockerHub: $IMAGE_NAME" + +# Соберите образ +docker build -t $IMAGE_NAME . + +# Загрузите в DockerHub (нужно предварительно создать репозиторий на hub.docker.com) +docker push $IMAGE_NAME +``` + +![img15](kubernetes-kubeadm/img/img15.png) + +## 🎯 После загрузки образа: Развёртывание в Kubernetes +Следующий шаг — применить манифесты к кластеру: + +### Скопируйте манифесты на мастер-ноду + +```bash +# На локальной машине скопируйте файлы на мастер +scp ~/git/homework/devops-diplom-yandexcloud/kubernetes-kubeadm/k8s/deployment.yaml \ + ~/git/homework/devops-diplom-yandexcloud/kubernetes-kubeadm/k8s/service.yaml \ + yc-user@89.169.133.106:~/ +``` + +### Проверка работы нод и сервиса + +![img16](kubernetes-kubeadm/img/img16.png) +![img17](kubernetes-kubeadm/img/img17.png) + +### Результаты этапа +- ✅ Создан репозиторий с тестовым приложением +- ✅ Подготовлен Dockerfile на базе nginx:alpine +- ✅ Образ опубликован на DockerHub: nastya2005/diploma-test-app +- ✅ Настроен CI/CD pipeline для автоматической сборки +- ✅ При создании тега/релиза происходит автоматический деплой в Kubernetes + +---- + +### Подготовка cистемы мониторинга и деплой приложения Уже должны быть готовы конфигурации для автоматического создания облачной инфраструктуры и поднятия Kubernetes кластера. Теперь необходимо подготовить конфигурационные файлы для настройки нашего Kubernetes кластера. @@ -102,6 +788,214 @@ Способ выполнения: 1. Воспользоваться пакетом [kube-prometheus](https://github.com/prometheus-operator/kube-prometheus), который уже включает в себя [Kubernetes оператор](https://operatorhub.io/) для [grafana](https://grafana.com/), [prometheus](https://prometheus.io/), [alertmanager](https://github.com/prometheus/alertmanager) и [node_exporter](https://github.com/prometheus/node_exporter). Альтернативный вариант - использовать набор helm чартов от [bitnami](https://github.com/bitnami/charts/tree/main/bitnami). + +# ЭТАП 4: СИСТЕМА МОНИТОРИНГА + +## 4.1. Архитектура мониторинга + +```text +┌─────────────────────────────────────────────────────────────┐ +│ Monitoring Stack (Namespace: monitoring) │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ ┌──────────────────────────────────────────────────┐ │ +│ │ Prometheus Server │ │ +│ │ - Сбор метрик каждые 30s │ │ +│ │ - Хранение: 15 дней │ │ +│ │ - Порт: 9090 │ │ +│ │ - Resources: 256Mi-512Mi RAM │ │ +│ └──────────────────────────────────────────────────┘ │ +│ │ +│ ┌──────────────────────────────────────────────────┐ │ +│ │ Grafana │ │ +│ │ - Визуализация и дашборды │ │ +│ │ - Порт: 3000 │ │ +│ │ - Pre-installed dashboards: │ │ +│ │ • Kubernetes Cluster Monitoring │ │ +│ │ • Kubernetes Node Monitoring │ │ +│ │ • Prometheus Overview │ │ +│ └──────────────────────────────────────────────────┘ │ +│ │ +│ ┌──────────────────────────────────────────────────┐ │ +│ │ Alertmanager │ │ +│ │ - Управление алертами │ │ +│ │ - Порт: 9093 │ │ +│ └──────────────────────────────────────────────────┘ │ +│ │ +│ ┌──────────────────────────────────────────────────┐ │ +│ │ Node Exporter (DaemonSet) │ │ +│ │ - Запущен на всех 3 нодах │ │ +│ │ - Сбор метрик: CPU, Memory, Disk, Network │ │ +│ │ - Порт: 9100 │ │ +│ └──────────────────────────────────────────────────┘ │ +│ │ +│ ┌──────────────────────────────────────────────────┐ │ +│ │ kube-prometheus-stack (Helm Chart) │ │ +│ │ Version: 82.13.0 │ │ +│ │ App Version: 0.89.0 │ │ +│ └──────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +## Структура файлов для этапа 4 + +```text +~/git/homework/devops-diplom-yandexcloud/monitoring/ +├── README.md # Описание этапа +├── helm-values/ +│ ├── prometheus-values.yaml # Настройки Prometheus +│ ├── grafana-values.yaml # Настройки Grafana +│ ├── alertmanager-values.yaml # Настройки Alertmanager +│ └── node-exporter-values.yaml # Настройки node-exporter +├── manifests/ +│ ├── namespace.yaml # Пространство имён monitoring +│ └── ingress.yaml # (опционально) доступ к Grafana +``` + +### 🔧 Шаг 1: Подготовка — установка Helm (если не установлен) + +```bash +# Проверьте, установлен ли Helm +helm version + +# Если нет — установите: +curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash + +# Проверьте установку +helm version +# ✅ Ожидаемый вывод: version.BuildInfo{Version:"v3.x.x", ...} +``` + +![img1](monitoring/img/img1.png) + +### 🔧 Шаг 2: Создайте структуру папок и файлы + +```bash +# Создайте папку monitoring в основном проекте +mkdir -p ~/git/homework/devops-diplom-yandexcloud/monitoring/{helm-values,manifests,scripts} +cd ~/git/homework/devops-diplom-yandexcloud/monitoring +``` + +### [namespace.yaml](/monitoring/manifests/namespace.yaml) +### [prometheus-values.yaml](/monitoring/helm-values/prometheus-values.yaml) +### [grafana-values.yaml](/monitoring/helm-values/grafana-values.yaml) +### [alertmanager-values.yaml](/monitoring/helm-values/alertmanager-values.yaml) +### [node-exporter-values.yaml](/monitoring/helm-values/node-exporter-values.yaml) + +### 🔧 Шаг 3: Добавьте Helm репозитории + +```bash +# Добавьте репозиторий Bitnami +helm repo add prometheus-community https://prometheus-community.github.io/helm-charts + +# Обновите индекс репозиториев +helm repo update +``` +![img1](monitoring/img/img2.png) + +### 🔧 Шаг 4: Примените namespace и установите компоненты +- 4.1. Создайте namespace monitoring + +```bash +# Скопировать файл +scp ~/git/homework/devops-diplom-yandexcloud/monitoring/manifests/namespace.yaml \ + yc-user@89.169.133.106:~/namespace.yaml + +# Подключитесь к мастер-ноде +ssh yc-user@89.169.133.106 + +# Примените namespace +kubectl apply -f namespace.yaml +``` +![img3](monitoring/img/img3.png) + +- 4.2. Установка мониторинга (kube-prometheus-stack) + +```bash + +# === НА МАСТЕР-НОДЕ (через SSH) === + +# 1. Убедитесь, что Helm установлен +helm version + +# 2. Добавьте репозиторий (если ещё не добавили) +helm repo add prometheus-community https://prometheus-community.github.io/helm-charts +helm repo update + +# 3. Примените namespace (файл уже скопирован) +kubectl apply -f ~/namespace.yaml + +# 4. Установите весь стек мониторинга ОДНОЙ КОМАНДОЙ: +helm install monitoring prometheus-community/kube-prometheus-stack \ + --namespace monitoring \ + --create-namespace \ + --set prometheus.prometheusSpec.retention=15d \ + --set prometheus.prometheusSpec.resources.requests.memory=256Mi \ + --set prometheus.prometheusSpec.resources.limits.memory=512Mi \ + --set grafana.adminPassword=diploma2024 \ + --set grafana.resources.requests.memory=128Mi \ + --set grafana.resources.limits.memory=256Mi \ + --set alertmanager.resources.requests.memory=64Mi \ + --set nodeExporter.resources.requests.memory=32Mi \ + --wait --timeout 15m + +# 5. Проверьте установку +kubectl get pods -n monitoring -o wide + +helm list -n monitoring +``` +![img4](monitoring/img/img4.png) +![img5](monitoring/img/img5.png) + +```bash +# Получите пароль для Grafana +GRAFANA_PASSWORD=$(kubectl get secret -n monitoring monitoring-grafana -o jsonpath="{.data.admin-password}" | base64 --decode) +echo "Grafana admin password: $GRAFANA_PASSWORD" +``` +![img5](monitoring/img/img6.png) + +### Настройте доступ к Grafana + +```bash +# На мастер-ноде измените тип сервиса Grafana на NodePort: +kubectl patch svc monitoring-grafana -n monitoring -p '{"spec": {"type": "NodePort", "ports": [{"port": 80, "nodePort": 30001}]}}' + +# Проверьте: +kubectl get svc -n monitoring | grep grafana +# ✅ Ожидаемый вывод: +# monitoring-grafana NodePort 10.96.xxx.xxx 80:30001/TCP 15m + +# Откройте в браузере на локальной машине: +# http://89.169.133.106:30001 +# Логин: admin +# Пароль: $GRAFANA_PASSWORD +``` +![img7](monitoring/img/img7.png) +![img9](monitoring/img/img9.png) + +### Проверьте Prometheus UI + +```bash +# Аналогично настройте доступ к Prometheus (NodePort): +kubectl patch svc monitoring-kube-prometheus-prometheus -n monitoring -p '{"spec": {"type": "NodePort", "ports": [{"port": 9090, "nodePort": 30002}]}}' + +# Откройте в браузере: +# http://89.169.133.106:30002 + +# Перейдите в: Status → Targets + +``` +![img8](monitoring/img/img8.png) + +### Результаты этапа +- ✅ Развёрнут полный стек мониторинга через Helm +- ✅ Prometheus собирает метрики со всех нод и подов +- ✅ Grafana имеет предконфигурированные дашборды +- ✅ Node Exporter запущен на всех 3 нодах +- ✅ Настроено хранение метрик на 15 дней +- ✅ Обеспечен HTTP доступ ко всем интерфейсам +---- + ### Деплой инфраструктуры в terraform pipeline 1. Если на первом этапе вы не воспользовались [Terraform Cloud](https://app.terraform.io/), то задеплойте и настройте в кластере [atlantis](https://www.runatlantis.io/) для отслеживания изменений инфраструктуры. Альтернативный вариант 3 задания: вместо Terraform Cloud или atlantis настройте на автоматический запуск и применение конфигурации terraform из вашего git-репозитория в выбранной вами CI-CD системе при любом комите в main ветку. Предоставьте скриншоты работы пайплайна из CI/CD системы. @@ -113,6 +1007,76 @@ 4. Http доступ на 80 порту к тестовому приложению. 5. Atlantis или terraform cloud или ci/cd-terraform --- + +# ЭТАП 5: TERRAFORM PIPELINE + +## 5.1. Архитектура CI/CD для инфраструктуры + +```text +┌─────────────────────────────────────────────────────────────┐ +│ Terraform Pipeline (GitHub Actions) │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ Workflow 1: terraform-plan.yml │ +│ ┌────────────────────────────────────────────────┐ │ +│ │ Trigger: Pull Request to main │ │ +│ │ Steps: │ │ +│ │ 1. Checkout code │ │ +│ │ 2. Setup Terraform │ │ +│ │ 3. Terraform Init │ │ +│ │ 4. Terraform Validate │ │ +│ │ 5. Terraform Plan │ │ +│ │ 6. Comment PR with plan output │ │ +│ └────────────────────────────────────────────────┘ │ +│ │ +│ Workflow 2: terraform-apply.yml │ +│ ┌────────────────────────────────────────────────┐ │ +│ │ Trigger: Push to main │ │ +│ │ Steps: │ │ +│ │ 1. Checkout code │ │ +│ │ 2. Setup Terraform │ │ +│ │ 3. Terraform Init │ │ +│ │ 4. Terraform Validate │ │ +│ │ 5. Terraform Plan │ │ +│ │ 6. Terraform Apply (auto-approve) │ │ +│ │ 7. Update outputs │ │ +│ └────────────────────────────────────────────────┘ │ +│ │ +│ Secrets: │ +│ - YC_TOKEN: IAM токен Yandex Cloud │ +│ - TF_BUCKET_NAME: S3 bucket для state │ +│ - TF_ACCESS_KEY: Access key для S3 │ +│ - TF_SECRET_KEY: Secret key для S3 │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` +### Шаг 5.2: Создайте директорию для GitHub Actions +```bash +# Создайте папку для рабочих процессов +mkdir -p .github/workflows +``` +### [terraform-plan.yml](/.github/workflows/terraform-plan.yml) +### [terraform-apply.yml](/.github/workflows/terraform-apply.yml) + +### Шаг 5.3: Настройте Secrets в GitHub +В репозитории на GitHub: + + 1. Откройте: Settings → Secrets and variables → Actions + 2. Добавьте следующие Repository secrets: + +![img8](monitoring/img/img10.png) + +### Шаг 5.4 Пушим в репозиторий + +![img11](monitoring/img/img11.png) + +### 5.4. Результаты этапа +- ✅ Настроен GitHub Actions для автоматизации Terraform +- ✅ При создании PR выполняется terraform plan с комментарием +- ✅ При merge в main автоматически применяется terraform apply +- ✅ Все секреты хранятся в зашифрованном виде +- ✅ Обеспечена полная трассируемость изменений инфраструктуры + ### Установка и настройка CI/CD Осталось настроить ci/cd систему для автоматической сборки docker image и деплоя приложения при изменении кода. @@ -130,6 +1094,198 @@ 2. При любом коммите в репозиторие с тестовым приложением происходит сборка и отправка в регистр Docker образа. 3. При создании тега (например, v1.0.0) происходит сборка и отправка с соответствующим label в регистри, а также деплой соответствующего Docker образа в кластер Kubernetes. + +# ЭТАП 6: CI/CD ПРИЛОЖЕНИЯ + +## 6.1. Архитектура пайплайна приложения + +```text +┌─────────────────────────────────────────────────────────────┐ +│ Application CI/CD Pipeline (GitHub Actions) │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ Event: Push to main │ +│ ┌────────────────────────────────────────────────┐ │ +│ │ Job: Build and Push Image │ │ +│ │ 1. Checkout code │ │ +│ │ 2. Setup Docker Buildx │ │ +│ │ 3. Login to DockerHub │ │ +│ │ 4. Build Docker image │ │ +│ │ 5. Push to DockerHub │ │ +│ │ - nastya2005/diploma-test-app:latest │ │ +│ │ - nastya2005/diploma-test-app: │ │ +│ └────────────────────────────────────────────────┘ │ +│ │ +│ Event: Release Published (Tag v*) │ +│ ┌────────────────────────────────────────────────┐ │ +│ │ Job: Build and Push Image (same as above) │ │ +│ │ + Tag: nastya2005/diploma-test-app:v1.0.0 │ │ +│ └────────────────────────────────────────────────┘ │ +│ │ │ +│ ↓ │ +│ ┌────────────────────────────────────────────────┐ │ +│ │ Job: Deploy to Kubernetes │ │ +│ │ 1. Checkout code │ │ +│ │ 2. Setup kubectl │ │ +│ │ 3. Configure kubeconfig │ │ +│ │ 4. kubectl set image deployment │ │ +│ │ 5. kubectl rollout status (wait) │ │ +│ │ 6. Verify deployment │ │ +│ └────────────────────────────────────────────────┘ │ +│ │ +│ Secrets: │ +│ - DOCKERHUB_USERNAME: nastya2005 │ +│ - DOCKERHUB_TOKEN: dckr_pat_xxxxx │ +│ - KUBE_CONFIG: base64-encoded kubeconfig │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +### Шаг 1: Подготовьте репозиторий приложения +```bash +# Перейдите в репозиторий приложения +cd ~/git/diploma-test-app + +# Проверьте текущее состояние +git status +git branch + +# Убедитесь, что в main +git checkout main +``` +### Шаг 2: Создайте директорию для GitHub Actions +```bash +# Создайте папку для workflow файлов +mkdir -p .github/workflows +``` +### Шаг 3: Создайте workflow файл ci-cd.yml + +### [ci-cd.yml](https://github.com/BaryshkovMikhail/diploma-test-app/.github/workflows/ci-cd.yml) + +### Шаг 4: Настройте Secrets в репозитории приложения + +4.1. Получите DockerHub Token + + Откройте: https://hub.docker.com/settings/security + Нажмите New Access Token + Введите название (например, github-actions) + Скопируйте токен (начинается с dckr_pat_...) + +4.2. Получите KUBE_CONFIG + +```bash +# На мастер-ноде Kubernetes выполните: +ssh yc-user@89.169.133.106 + +# Закодируйте kubeconfig в base64 +cat ~/.kube/config | base64 -w0 + +# Скопируйте вывод (длинная строка) +``` + +4.3. Добавьте секреты в GitHub +```bash +1. Откройте репозиторий: https://github.com/BaryshkovMikhail/diploma-test-app +2. Перейдите: Settings → Secrets and variables → Actions +3. Нажмите New repository secret + Добавьте три секрета: +``` +![img12](monitoring/img/img12.png) + +### Шаг 5: Создайте релиз для деплоя +```bash +cd ~/git/diploma-test-app + +# Создайте тег +git tag v1.0.0 + +# Запушите тег (это запустит деплой!) +git push origin v1.0.0 + +``` +![img13](monitoring/img/img13.png) + +### Результаты этапа +- ✅ Настроен полный CI/CD pipeline для приложения +- ✅ При коммите в main автоматически собирается образ +- ✅ При создании тега/релиза происходит деплой в Kubernetes +- ✅ Используется rolling update для zero-downtime деплоя +- ✅ Все секреты безопасно хранятся в GitHub Secrets +- Обеспечена полная автоматизация от кода до production + +# ЗАКЛЮЧЕНИЕ +## Итоги проекта +В ходе выполнения дипломного проекта успешно решены следующие задачи: + +1. ✅ Инфраструктура как код + Вся инфраструктура Yandex Cloud (сети, ВМ, сервисные аккаунты, S3 bucket) описана в Terraform и управляется через Git. +2. ✅ Kubernetes кластер + Развёрнут самоуправляемый Kubernetes кластер через kubeadm с 3 нодами (1 master + 2 workers) с использованием containerd и Calico CNI. +3. ✅ Тестовое приложение + Создано nginx-приложение с health checks, опубликован Docker-образ на DockerHub. +4. ✅ Система мониторинга + Развёрнут полный стек мониторинга (Prometheus + Grafana + Alertmanager + Node Exporter) через Helm chart kube-prometheus-stack. +5. ✅ CI/CD для инфраструктуры + Настроены GitHub Actions workflows для автоматического применения Terraform конфигурации при изменениях в main ветке. +6. ✅ CI/CD для приложения + Настроена автоматическая сборка Docker-образов и деплой в Kubernetes при создании релизов + +## +Основная инфраструктура +```text +devops-diplom-yandexcloud/ +├── bootstrap/ +│ ├── main.tf +│ ├── variables.tf +│ ├── outputs.tf +│ └── providers.tf +├── infrastructure/ +│ ├── main.tf +│ ├── variables.tf +│ ├── outputs.tf +│ └── providers.tf +├── kubernetes-kubeadm/ +│ ├── main.tf +│ ├── variables.tf +│ ├── outputs.tf +│ ├── providers.tf +│ ├── terraform.tfvars +│ └── ansible/ +│ ├── hosts.ini +│ ├── install-node.sh +│ └── init-cluster.sh +├── registry/ +│ ├── main.tf +│ ├── variables.tf +│ ├── outputs.tf +│ └── providers.tf +├── monitoring/ +│ ├── manifests/ +│ │ └── namespace.yaml +│ └── helm-values/ +│ ├── prometheus-values.yaml +│ ├── grafana-values.yaml +│ ├── alertmanager-values.yaml +│ └── node-exporter-values.yaml +├── .github/workflows/ +│ ├── terraform-apply.yml +│ └── terraform-plan.yml +└── README.md +``` + +## Тестовое приложение +```text +diploma-test-app/ +├── Dockerfile +├── nginx.conf +├── index.html +├── .dockerignore +├── .gitignore +├── README.md +└── .github/workflows/ + └── ci-cd.yml +``` + --- ## Что необходимо для сдачи задания? diff --git a/bootstrap/.gitignore b/bootstrap/.gitignore new file mode 100644 index 000000000..39a8aa959 --- /dev/null +++ b/bootstrap/.gitignore @@ -0,0 +1,3 @@ +*.tfplan +tfplan +tfplan.* \ No newline at end of file diff --git a/bootstrap/.terraform.lock.hcl b/bootstrap/.terraform.lock.hcl new file mode 100644 index 000000000..34f61829d --- /dev/null +++ b/bootstrap/.terraform.lock.hcl @@ -0,0 +1,10 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/yandex-cloud/yandex" { + version = "0.192.0" + constraints = "~> 0.120" + hashes = [ + "h1:0GUB8lhw67A7fOzJQ6VO/2vI9SFVBWEGM4aRty0uJN4=", + ] +} diff --git a/bootstrap/README.md b/bootstrap/README.md new file mode 100644 index 000000000..6d1058ba4 --- /dev/null +++ b/bootstrap/README.md @@ -0,0 +1 @@ +# Trigger Terraform Apply diff --git a/bootstrap/img/img1.png b/bootstrap/img/img1.png new file mode 100644 index 000000000..a4e7c89ee Binary files /dev/null and b/bootstrap/img/img1.png differ diff --git a/bootstrap/img/img2.png b/bootstrap/img/img2.png new file mode 100644 index 000000000..b0320092a Binary files /dev/null and b/bootstrap/img/img2.png differ diff --git a/bootstrap/img/img3.png b/bootstrap/img/img3.png new file mode 100644 index 000000000..517e781ff Binary files /dev/null and b/bootstrap/img/img3.png differ diff --git a/bootstrap/img/img4-1.png b/bootstrap/img/img4-1.png new file mode 100644 index 000000000..6d530ca5b Binary files /dev/null and b/bootstrap/img/img4-1.png differ diff --git a/bootstrap/img/img4.png b/bootstrap/img/img4.png new file mode 100644 index 000000000..20fe3d178 Binary files /dev/null and b/bootstrap/img/img4.png differ diff --git a/bootstrap/img/img5.png b/bootstrap/img/img5.png new file mode 100644 index 000000000..61c794467 Binary files /dev/null and b/bootstrap/img/img5.png differ diff --git a/bootstrap/img/img6.png b/bootstrap/img/img6.png new file mode 100644 index 000000000..9d510857b Binary files /dev/null and b/bootstrap/img/img6.png differ diff --git a/bootstrap/img/img7.png b/bootstrap/img/img7.png new file mode 100644 index 000000000..836f7aa06 Binary files /dev/null and b/bootstrap/img/img7.png differ diff --git a/bootstrap/main.tf b/bootstrap/main.tf new file mode 100644 index 000000000..5de5d34cc --- /dev/null +++ b/bootstrap/main.tf @@ -0,0 +1,78 @@ +# ============================================================ +# 🎯 ЦЕЛЬ: Создать минимальный набор ресурсов для работы Terraform +# ============================================================ + +# 🎭 Сервисный аккаунт для управления инфраструктурой +# ⚠️ Внимание: yandex_iam_service_account НЕ поддерживает tags/labels +resource "yandex_iam_service_account" "terraform" { + folder_id = var.yandex_folder_id + name = "${var.project_name}-${var.environment}-terraform-sa" + description = "Service account for Terraform infrastructure automation" +} + +# 🔐 Назначение роли "editor" сервисному аккаунту +resource "yandex_resourcemanager_folder_iam_member" "terraform_editor" { + folder_id = var.yandex_folder_id + role = "editor" + member = "serviceAccount:${yandex_iam_service_account.terraform.id}" +} + +# 🔑 Статический ключ доступа для S3 Object Storage +resource "yandex_iam_service_account_static_access_key" "terraform_s3_key" { + service_account_id = yandex_iam_service_account.terraform.id + description = "Static access key for S3 backend storage" +} + +# 🔐 KMS ключ для шифрования bucket (как в вашем рабочем примере) +resource "yandex_kms_symmetric_key" "tfstate_key" { + name = "${var.project_name}-${var.environment}-tfstate-key" + description = "KMS key for terraform state encryption" + default_algorithm = "AES_128" + rotation_period = "8760h" # 1 год + deletion_protection = false +} + +# 🪣 Bucket для хранения terraform state +resource "yandex_storage_bucket" "tfstate" { + bucket = var.bucket_name + folder_id = var.yandex_folder_id # ✅ Обязательно для корректного биллинга + force_destroy = false + + # ⚠️ acl deprecated, но пока работает. Для продакшена использовать yandex_storage_bucket_grant + acl = "private" + + # 🔒 Шифрование с использованием KMS (как в вашем рабочем примере) + server_side_encryption_configuration { + rule { + apply_server_side_encryption_by_default { + kms_master_key_id = yandex_kms_symmetric_key.tfstate_key.id + sse_algorithm = "aws:kms" # ✅ Единственное допустимое значение + } + } + } + + # 🌐 Разрешаем публичный доступ на чтение (опционально, для отладки) + anonymous_access_flags { + read = false # ❌ false для приватного state-файла + list = false + } + + # 🗑️ Политика жизненного цикла + lifecycle_rule { + id = "cleanup_old_state_versions" + enabled = true + + noncurrent_version_expiration { + days = 30 + } + } + + # 🌐 CORS конфигурация (внутри ресурса, как требует провайдер) + cors_rule { + allowed_headers = ["*"] + allowed_methods = ["GET", "PUT", "POST", "DELETE", "HEAD"] + allowed_origins = ["*"] + expose_headers = ["ETag"] + max_age_seconds = 3600 + } +} \ No newline at end of file diff --git a/bootstrap/outputs.tf b/bootstrap/outputs.tf new file mode 100644 index 000000000..f11607785 --- /dev/null +++ b/bootstrap/outputs.tf @@ -0,0 +1,51 @@ +output "service_account_id" { + description = "ID сервисного аккаунта" + value = yandex_iam_service_account.terraform.id +} + +output "bucket_name" { + description = "Имя bucket для terraform state" + value = yandex_storage_bucket.tfstate.bucket +} + +output "bucket_endpoint" { + description = "S3 endpoint" + value = "https://storage.yandexcloud.net" +} + +output "backend_access_key" { + description = "Access key для backend" + value = yandex_iam_service_account_static_access_key.terraform_s3_key.access_key + sensitive = true +} + +output "backend_secret_key" { + description = "Secret key для backend" + value = yandex_iam_service_account_static_access_key.terraform_s3_key.secret_key + sensitive = true +} + +output "kms_key_id" { + description = "ID KMS ключа для шифрования" + value = yandex_kms_symmetric_key.tfstate_key.id +} + + +output "next_step_instructions" { + description = "Инструкция по переходу к этапу инфраструктуры" + value = < subnet.id } +} + +output "security_group_id" { + description = "ID security group для Kubernetes узлов" + value = yandex_vpc_security_group.k8s_nodes.id +} + +output "network_cidr" { + description = "CIDR-блок основной сети" + value = var.vpc_cidr +} + +# 📋 Сводка для диплома +output "infrastructure_summary" { + description = "Краткая сводка созданной инфраструктуры" + value = < /dev/null +sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml +sudo systemctl restart containerd +sudo systemctl enable containerd + +# 5. Подготовка репозитория Kubernetes +echo "[5/8] Adding Kubernetes repository..." +sudo mkdir -p /etc/apt/keyrings +curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.28/deb/Release.key | \ + sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg +echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.28/deb/ /" | \ + sudo tee /etc/apt/sources.list.d/kubernetes.list + +# 6. Установка kubeadm, kubelet, kubectl +echo "[6/8] Installing kubelet, kubeadm, kubectl..." +sudo apt-get update -qq +sudo apt-get install -y kubelet kubeadm kubectl +sudo apt-mark hold kubelet kubeadm kubectl + +# 7. Отключение swap (требуется для kubelet) +echo "[7/8] Disabling swap..." +sudo swapoff -a +sudo sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab + +# 8. Запуск kubelet +echo "[8/8] Starting kubelet..." +sudo systemctl enable --now kubelet + +# Проверка +echo "" +echo "=== Verification ===" +echo "Containerd: $(containerd --version 2>/dev/null | head -1 || echo 'not found')" +echo "Kubeadm: $(kubeadm version -o short 2>/dev/null || echo 'not found')" +echo "Kubelet: $(kubelet --version 2>/dev/null || echo 'not found')" +echo "Kubectl: $(kubectl version --client -o json 2>/dev/null | jq -r '.clientVersion.gitVersion' 2>/dev/null || echo 'not found')" +echo "" +echo "✅ Node setup completed on $(hostname)" \ No newline at end of file diff --git a/kubernetes-kubeadm/img/img1.png b/kubernetes-kubeadm/img/img1.png new file mode 100644 index 000000000..85e81383c Binary files /dev/null and b/kubernetes-kubeadm/img/img1.png differ diff --git a/kubernetes-kubeadm/img/img10.png b/kubernetes-kubeadm/img/img10.png new file mode 100644 index 000000000..a38bd22ce Binary files /dev/null and b/kubernetes-kubeadm/img/img10.png differ diff --git a/kubernetes-kubeadm/img/img11.png b/kubernetes-kubeadm/img/img11.png new file mode 100644 index 000000000..fda754701 Binary files /dev/null and b/kubernetes-kubeadm/img/img11.png differ diff --git a/kubernetes-kubeadm/img/img12.png b/kubernetes-kubeadm/img/img12.png new file mode 100644 index 000000000..bc207bd4c Binary files /dev/null and b/kubernetes-kubeadm/img/img12.png differ diff --git a/kubernetes-kubeadm/img/img13.png b/kubernetes-kubeadm/img/img13.png new file mode 100644 index 000000000..a17de922f Binary files /dev/null and b/kubernetes-kubeadm/img/img13.png differ diff --git a/kubernetes-kubeadm/img/img14.png b/kubernetes-kubeadm/img/img14.png new file mode 100644 index 000000000..b6eb017b9 Binary files /dev/null and b/kubernetes-kubeadm/img/img14.png differ diff --git a/kubernetes-kubeadm/img/img15.png b/kubernetes-kubeadm/img/img15.png new file mode 100644 index 000000000..d98b45f00 Binary files /dev/null and b/kubernetes-kubeadm/img/img15.png differ diff --git a/kubernetes-kubeadm/img/img16.png b/kubernetes-kubeadm/img/img16.png new file mode 100644 index 000000000..69ed7273e Binary files /dev/null and b/kubernetes-kubeadm/img/img16.png differ diff --git a/kubernetes-kubeadm/img/img17.png b/kubernetes-kubeadm/img/img17.png new file mode 100644 index 000000000..1752326c3 Binary files /dev/null and b/kubernetes-kubeadm/img/img17.png differ diff --git a/kubernetes-kubeadm/img/img2.png b/kubernetes-kubeadm/img/img2.png new file mode 100644 index 000000000..4e14af05e Binary files /dev/null and b/kubernetes-kubeadm/img/img2.png differ diff --git a/kubernetes-kubeadm/img/img3-1.png b/kubernetes-kubeadm/img/img3-1.png new file mode 100644 index 000000000..c5fd0cfcc Binary files /dev/null and b/kubernetes-kubeadm/img/img3-1.png differ diff --git a/kubernetes-kubeadm/img/img3-2.png b/kubernetes-kubeadm/img/img3-2.png new file mode 100644 index 000000000..617c57d2c Binary files /dev/null and b/kubernetes-kubeadm/img/img3-2.png differ diff --git a/kubernetes-kubeadm/img/img3-3.png b/kubernetes-kubeadm/img/img3-3.png new file mode 100644 index 000000000..a935936ae Binary files /dev/null and b/kubernetes-kubeadm/img/img3-3.png differ diff --git a/kubernetes-kubeadm/img/img3-4.png b/kubernetes-kubeadm/img/img3-4.png new file mode 100644 index 000000000..c2ebddd4c Binary files /dev/null and b/kubernetes-kubeadm/img/img3-4.png differ diff --git a/kubernetes-kubeadm/img/img4-1.png b/kubernetes-kubeadm/img/img4-1.png new file mode 100644 index 000000000..2da0d7850 Binary files /dev/null and b/kubernetes-kubeadm/img/img4-1.png differ diff --git a/kubernetes-kubeadm/img/img4-2.png b/kubernetes-kubeadm/img/img4-2.png new file mode 100644 index 000000000..6ba30d7eb Binary files /dev/null and b/kubernetes-kubeadm/img/img4-2.png differ diff --git a/kubernetes-kubeadm/img/img4-3.png b/kubernetes-kubeadm/img/img4-3.png new file mode 100644 index 000000000..25043c290 Binary files /dev/null and b/kubernetes-kubeadm/img/img4-3.png differ diff --git a/kubernetes-kubeadm/img/img4-4.png b/kubernetes-kubeadm/img/img4-4.png new file mode 100644 index 000000000..6c21dbe8b Binary files /dev/null and b/kubernetes-kubeadm/img/img4-4.png differ diff --git a/kubernetes-kubeadm/img/img5-1.png b/kubernetes-kubeadm/img/img5-1.png new file mode 100644 index 000000000..42642d4f0 Binary files /dev/null and b/kubernetes-kubeadm/img/img5-1.png differ diff --git a/kubernetes-kubeadm/img/img5-2.png b/kubernetes-kubeadm/img/img5-2.png new file mode 100644 index 000000000..545cc25d9 Binary files /dev/null and b/kubernetes-kubeadm/img/img5-2.png differ diff --git a/kubernetes-kubeadm/img/img5-3.png b/kubernetes-kubeadm/img/img5-3.png new file mode 100644 index 000000000..ec278d362 Binary files /dev/null and b/kubernetes-kubeadm/img/img5-3.png differ diff --git a/kubernetes-kubeadm/img/img5-4.png b/kubernetes-kubeadm/img/img5-4.png new file mode 100644 index 000000000..99632c517 Binary files /dev/null and b/kubernetes-kubeadm/img/img5-4.png differ diff --git a/kubernetes-kubeadm/img/img6-1.png b/kubernetes-kubeadm/img/img6-1.png new file mode 100644 index 000000000..f08ece086 Binary files /dev/null and b/kubernetes-kubeadm/img/img6-1.png differ diff --git a/kubernetes-kubeadm/img/img6-2.png b/kubernetes-kubeadm/img/img6-2.png new file mode 100644 index 000000000..779edbecd Binary files /dev/null and b/kubernetes-kubeadm/img/img6-2.png differ diff --git a/kubernetes-kubeadm/img/img6-3.png b/kubernetes-kubeadm/img/img6-3.png new file mode 100644 index 000000000..eb73c84e5 Binary files /dev/null and b/kubernetes-kubeadm/img/img6-3.png differ diff --git a/kubernetes-kubeadm/img/img6-4.png b/kubernetes-kubeadm/img/img6-4.png new file mode 100644 index 000000000..feeb2cae6 Binary files /dev/null and b/kubernetes-kubeadm/img/img6-4.png differ diff --git a/kubernetes-kubeadm/img/img7.png b/kubernetes-kubeadm/img/img7.png new file mode 100644 index 000000000..feeb2cae6 Binary files /dev/null and b/kubernetes-kubeadm/img/img7.png differ diff --git a/kubernetes-kubeadm/img/img8.png b/kubernetes-kubeadm/img/img8.png new file mode 100644 index 000000000..72a4b5a51 Binary files /dev/null and b/kubernetes-kubeadm/img/img8.png differ diff --git a/kubernetes-kubeadm/img/img9.png b/kubernetes-kubeadm/img/img9.png new file mode 100644 index 000000000..44cc0a876 Binary files /dev/null and b/kubernetes-kubeadm/img/img9.png differ diff --git a/kubernetes-kubeadm/k8s/deployment.yaml b/kubernetes-kubeadm/k8s/deployment.yaml new file mode 100644 index 000000000..64cf4984d --- /dev/null +++ b/kubernetes-kubeadm/k8s/deployment.yaml @@ -0,0 +1,82 @@ +# ============================================================ +# 🚀 Deployment для тестового приложения диплома +# Файл: kubernetes-kubeadm/k8s/deployment.yaml +# ============================================================ + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: diploma-test-app + labels: + app: diploma-test-app + project: diploma + environment: dev +spec: + replicas: 3 # 3 реплики для отказоустойчивости + selector: + matchLabels: + app: diploma-test-app + template: + metadata: # ✅ Исправлено: было "meta", должно быть "metadata" + labels: + app: diploma-test-app + project: diploma + spec: + # 🔐 Service account для доступа к реестру (если нужен) + # serviceAccountName: k8s-node-sa + + containers: + - name: nginx + # 📦 Образ: замените на реальный ID из terraform output + image: nastya2005/diploma-test-app:latest + imagePullPolicy: Always # Всегда тянуть свежий образ + + # 🔓 Порты + ports: + - name: http + containerPort: 80 + protocol: TCP + + # 🏥 Health checks для Kubernetes + readinessProbe: + httpGet: + path: /ready + port: http + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 3 + successThreshold: 1 + failureThreshold: 3 + + livenessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 15 + periodSeconds: 20 + timeoutSeconds: 3 + successThreshold: 1 + failureThreshold: 3 + + # 💾 Ресурсы (ограничения для экономии) + resources: + requests: + memory: "64Mi" + cpu: "50m" + limits: + memory: "128Mi" + cpu: "100m" + + # 🏷️ Переменные окружения (опционально) + env: + - name: APP_NAME + value: "diploma-test-app" + - name: APP_VERSION + value: "1.0.0" + + + # 🛑 Политика перезапуска + restartPolicy: Always + + # ⏱️ Таймауты + terminationGracePeriodSeconds: 30 \ No newline at end of file diff --git a/kubernetes-kubeadm/k8s/ingress.yaml b/kubernetes-kubeadm/k8s/ingress.yaml new file mode 100644 index 000000000..1b306ffea --- /dev/null +++ b/kubernetes-kubeadm/k8s/ingress.yaml @@ -0,0 +1,45 @@ +# ============================================================ +# 🌐 Ingress для доступа к приложению из Интернета +# Файл: kubernetes-kubeadm/k8s/ingress.yaml +# Примечание: Требуется установленный Ingress Controller (nginx-ingress, traefik) +# ============================================================ + +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: diploma-test-app + labels: + app: diploma-test-app + # annotations: + # kubernetes.io/ingress.class: "nginx" + # cert-manager.io/cluster-issuer: "letsencrypt-prod" # Для HTTPS +spec: + # 🔐 TLS (опционально, требует cert-manager) + # tls: + # - hosts: + # - app.diploma.example.com + # secretName: diploma-tls-secret + + rules: + # 🌍 Если есть домен — раскомментируйте и замените + # - host: app.diploma.example.com + # http: + # paths: + # - path: / + # pathType: Prefix + # backend: + # service: + # name: diploma-test-app + # port: + # name: http + + # 🎯 Если домена нет — доступ по IP через default backend + - http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: diploma-test-app + port: + name: http \ No newline at end of file diff --git a/kubernetes-kubeadm/k8s/service.yaml b/kubernetes-kubeadm/k8s/service.yaml new file mode 100644 index 000000000..676f5262f --- /dev/null +++ b/kubernetes-kubeadm/k8s/service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + name: diploma-test-app +spec: + type: NodePort # ← Изменено с ClusterIP + selector: + app: diploma-test-app + ports: + - name: http + port: 80 + targetPort: 80 + nodePort: 30080 # ← Явно укажите порт в диапазоне 30000-32767 + protocol: TCP \ No newline at end of file diff --git a/kubernetes-kubeadm/main.tf b/kubernetes-kubeadm/main.tf new file mode 100644 index 000000000..205050de5 --- /dev/null +++ b/kubernetes-kubeadm/main.tf @@ -0,0 +1,107 @@ +data "yandex_compute_image" "ubuntu" { + family = "ubuntu-2004-lts" +} + +locals { + ssh_public_key = file(var.ssh_public_key_path) + + cloud_config = <<-EOF +#cloud-config +users: + - name: ${var.vm_username} + groups: sudo + shell: /bin/bash + sudo: ['ALL=(ALL) NOPASSWD:ALL'] + ssh-authorized-keys: + - ${local.ssh_public_key} +runcmd: + - apt-get update + - apt-get install -y curl wget vim git jq + - echo "Base setup completed on $(hostname)" +EOF +} + +# 🎯 Мастер-нода (НЕ прерываемая!) +resource "yandex_compute_instance" "master" { + name = "${var.project_name}-k8s-master" + hostname = "${var.project_name}-k8s-master" + zone = var.yandex_zone + description = "Kubernetes master node" + + resources { + cores = var.master_resources.cores + memory = var.master_resources.memory + core_fraction = var.master_resources.core_fraction + } + + boot_disk { + initialize_params { + image_id = data.yandex_compute_image.ubuntu.id + size = var.master_resources.disk_size + type = var.master_resources.disk_type + } + } + + network_interface { + subnet_id = var.subnet_id + security_group_ids = [var.security_group_id] + nat = true + } + + metadata = { + user-data = local.cloud_config + ssh-keys = "${var.vm_username}:${local.ssh_public_key}" + } + + scheduling_policy { + preemptible = false # Мастер НЕ должен прерываться + } + + labels = { + role = "master" + environment = var.environment + } +} + +# 👷 Рабочие ноды (МОГУТ быть прерываемыми) +resource "yandex_compute_instance" "worker" { + count = var.worker_count + name = "${var.project_name}-k8s-worker-${count.index + 1}" + hostname = "${var.project_name}-k8s-worker-${count.index + 1}" + zone = var.yandex_zone + description = "Kubernetes worker node" + + resources { + cores = var.worker_resources.cores + memory = var.worker_resources.memory + core_fraction = var.worker_resources.core_fraction + } + + boot_disk { + initialize_params { + image_id = data.yandex_compute_image.ubuntu.id + size = var.worker_resources.disk_size + type = var.worker_resources.disk_type + } + } + + network_interface { + subnet_id = var.subnet_id + security_group_ids = [var.security_group_id] + nat = true + } + + metadata = { + user-data = local.cloud_config + ssh-keys = "${var.vm_username}:${local.ssh_public_key}" + } + + scheduling_policy { + preemptible = var.use_preemptible # Рабочие МОГУТ прерываться + } + + labels = { + role = "worker" + environment = var.environment + } +} \ No newline at end of file diff --git a/kubernetes-kubeadm/outputs.tf b/kubernetes-kubeadm/outputs.tf new file mode 100644 index 000000000..9e63a4161 --- /dev/null +++ b/kubernetes-kubeadm/outputs.tf @@ -0,0 +1,45 @@ +output "master_external_ip" { + value = yandex_compute_instance.master.network_interface[0].nat_ip_address +} + +output "master_internal_ip" { + value = yandex_compute_instance.master.network_interface[0].ip_address +} + +output "worker_external_ips" { + value = [for w in yandex_compute_instance.worker : w.network_interface[0].nat_ip_address] +} + +output "worker_internal_ips" { + value = [for w in yandex_compute_instance.worker : w.network_interface[0].ip_address] +} + +output "ansible_inventory" { + value = <<-EOF +[master] +${yandex_compute_instance.master.network_interface[0].nat_ip_address} ansible_user=${var.vm_username} + +[workers] +%{ for ip in [for w in yandex_compute_instance.worker : w.network_interface[0].nat_ip_address] ~} +${ip} ansible_user=${var.vm_username} +%{ endfor ~} + +[all:children] +master +workers + +[all:vars] +ansible_ssh_common_args='-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null' +ansible_python_interpreter=/usr/bin/python3 +EOF +} + +output "ssh_commands" { + value = { + master = "ssh ${var.vm_username}@${yandex_compute_instance.master.network_interface[0].nat_ip_address}" + workers = [ + for i, w in yandex_compute_instance.worker : + "ssh ${var.vm_username}@${w.network_interface[0].nat_ip_address} # worker-${i + 1}" + ] + } +} \ No newline at end of file diff --git a/kubernetes-kubeadm/providers.tf b/kubernetes-kubeadm/providers.tf new file mode 100644 index 000000000..00f186646 --- /dev/null +++ b/kubernetes-kubeadm/providers.tf @@ -0,0 +1,39 @@ +# ============================================================ +# 🔄 Провайдер + Backend для kubeadm-кластера +# Backend указывает на bucket из bootstrap этапа +# ============================================================ + +terraform { + required_version = ">= 1.6" + + required_providers { + yandex = { + source = "yandex-cloud/yandex" + version = "~> 0.192" + } + } + + # 🗄️ Backend: S3 bucket в Yandex Object Storage + backend "s3" { + # Эти значения можно передать через -backend-config или оставить как переменные + # При инициализации: terraform init -backend-config="bucket=..." и т.д. + + endpoint = "https://storage.yandexcloud.net" # ✅ Правильный формат (как в infrastructure/) + region = "ru-central1" + key = "terraform/kubernetes-kubeadm.tfstate" + + # 🔧 Обход валидаций для Yandex Cloud + skip_region_validation = true + skip_credentials_validation = true + skip_requesting_account_id = true + skip_s3_checksum = true + } +} + +# Провайдер с использованием ключа сервисного аккаунта +provider "yandex" { + service_account_key_file = var.yandex_key_file + cloud_id = var.yandex_cloud_id + folder_id = var.yandex_folder_id + zone = var.yandex_zone +} diff --git a/kubernetes-kubeadm/variables.tf b/kubernetes-kubeadm/variables.tf new file mode 100644 index 000000000..c2e421e5c --- /dev/null +++ b/kubernetes-kubeadm/variables.tf @@ -0,0 +1,108 @@ +variable "yandex_cloud_id" { + type = string + sensitive = true +} + +variable "yandex_folder_id" { + type = string + sensitive = true +} + +variable "yandex_key_file" { + type = string + default = "terraform-sa-key.json" +} + +variable "vpc_network_id" { + type = string + default = "enpkueaodh8kh3117nnt" +} + +variable "subnet_id" { + type = string + default = "e9bc8gll7ocj29uv1ve6" +} + +variable "security_group_id" { + type = string + default = "enp8ktl4ok6a48g49uht" +} + +variable "yandex_zone" { + type = string + default = "ru-central1-a" +} + +variable "environment" { + type = string + default = "dev" +} + +variable "project_name" { + type = string + default = "diploma" +} + +variable "vm_username" { + type = string + default = "yc-user" +} + +variable "ssh_public_key_path" { + type = string + default = "~/.ssh/id_ed25519.pub" +} + +variable "master_resources" { + type = object({ + cores = number + memory = number + core_fraction = number + disk_size = number + disk_type = string + }) + default = { + cores = 2 + memory = 4 + core_fraction = 20 + disk_size = 20 + disk_type = "network-hdd" + } +} + +variable "worker_resources" { + type = object({ + cores = number + memory = number + core_fraction = number + disk_size = number + disk_type = string + }) + default = { + cores = 2 + memory = 2 + core_fraction = 20 + disk_size = 20 + disk_type = "network-hdd" + } +} + +variable "worker_count" { + type = number + default = 2 +} + +variable "use_preemptible" { + type = bool + default = true +} + +variable "k8s_version" { + type = string + default = "1.28" +} + +variable "pod_network_cidr" { + type = string + default = "10.244.0.0/16" +} \ No newline at end of file diff --git a/monitoring/helm-values/alertmanager-values.yaml b/monitoring/helm-values/alertmanager-values.yaml new file mode 100644 index 000000000..a87e0789b --- /dev/null +++ b/monitoring/helm-values/alertmanager-values.yaml @@ -0,0 +1,54 @@ +# ============================================================ +# Настройки Alertmanager для дипломного проекта +# Файл: monitoring/helm-values/alertmanager-values.yaml +# ============================================================ + +fullnameOverride: alertmanager + +# Ресурсы (минимальные для демо) +resources: + requests: + memory: 64Mi + cpu: 25m + limits: + memory: 128Mi + cpu: 50m + +# Хранение (для silences и notifications) +persistence: + enabled: true + size: 1Gi + storageClass: "network-hdd" + +# Service для доступа +service: + type: ClusterIP + port: 9093 + +# Настройка уведомлений (для диплома — логирование) +config: + global: + resolve_timeout: 5m + route: + group_by: ['alertname', 'job'] + group_wait: 30s + group_interval: 5m + repeat_interval: 12h + receiver: 'log-only' + routes: + - match: + severity: critical + receiver: 'log-only' + receivers: + - name: 'log-only' + # Для диплома: просто логируем алерты + # В продакшене: добавьте slack_configs, email_configs, etc. + webhook_configs: + - url: 'http://localhost:9094/log' # Заглушка для демо + +# Ingress (опционально) +# ingress: +# enabled: true +# className: nginx +# hosts: +# - alertmanager.diploma.local diff --git a/monitoring/helm-values/grafana-values.yaml b/monitoring/helm-values/grafana-values.yaml new file mode 100644 index 000000000..06061dec9 --- /dev/null +++ b/monitoring/helm-values/grafana-values.yaml @@ -0,0 +1,85 @@ +# ============================================================ +# Настройки Grafana для дипломного проекта +# Файл: monitoring/helm-values/grafana-values.yaml +# ============================================================ + +fullnameOverride: grafana + +# Ресурсы (экономия) +resources: + requests: + memory: 128Mi + cpu: 50m + limits: + memory: 256Mi + cpu: 100m + +# Хранение (для дашбордов и настроек) +persistence: + enabled: true + size: 2Gi + storageClass: "network-hdd" + +# Настройки входа (для диплома — простой пароль) +adminUser: admin +adminPassword: diploma2024 # ⚠️ В продакшене используйте Lockbox! + +# Service для доступа к Grafana UI +service: + type: ClusterIP + port: 80 + +# Ingress (опционально) +# ingress: +# enabled: true +# className: nginx +# hosts: +# - grafana.diploma.local +# paths: +# - path: / +# pathType: Prefix + +# Предварительно установленные дашборды +dashboards: + default: + # Kubernetes Cluster Monitoring + k8s-cluster: + gnetId: 315 + revision: 1 + datasource: Prometheus + # Kubernetes Node Monitoring + k8s-nodes: + gnetId: 13978 + revision: 1 + datasource: Prometheus + # Prometheus Monitoring + prometheus: + gnetId: 3662 + revision: 1 + datasource: Prometheus + +# Источники данных (Prometheus) +datasources: + datasources.yaml: + apiVersion: 1 + datasources: + - name: Prometheus + type: prometheus + url: http://prometheus-server.monitoring.svc.cluster.local:9090 + access: proxy + isDefault: true + editable: true + +# Плагины (опционально) +plugins: + - grafana-piechart-panel + - grafana-worldmap-panel + +# Sidecar для авто-обновления дашбордов +sidecar: + dashboards: + enabled: true + searchNamespace: ALL + datasources: + enabled: true + searchNamespace: ALL diff --git a/monitoring/helm-values/node-exporter-values.yaml b/monitoring/helm-values/node-exporter-values.yaml new file mode 100644 index 000000000..3f2fc950d --- /dev/null +++ b/monitoring/helm-values/node-exporter-values.yaml @@ -0,0 +1,40 @@ +# ============================================================ +# Настройки Node Exporter для дипломного проекта +# Файл: monitoring/helm-values/node-exporter-values.yaml +# ============================================================ + +fullnameOverride: node-exporter + +# Ресурсы (очень минимальные, так как агент лёгкий) +resources: + requests: + memory: 32Mi + cpu: 10m + limits: + memory: 64Mi + cpu: 50m + +# Запуск на всех нодах (включая master, для полной картины) +prometheus: + monitor: + enabled: true + # Интервал сбора метрик + scrapeInterval: 30s + # Дополнительные метрики для Kubernetes + additionalScrapeConfigs: [] + +# Настройки сбора метрик +extraArgs: + - --collector.filesystem.mount-points-exclude=^/(dev|proc|sys|var/lib/docker/.+)($|/) + - --collector.filesystem.fs-types-exclude=^(autofs|binfmt_misc|cgroup|configfs|debugfs|devpts|devtmpfs|fusectl|hugetlbfs|mqueue|overlay|proc|procfs|pstore|rpc_pipefs|securityfs|sysfs|tracefs)$ + +# Service для Prometheus +service: + port: 9100 + targetPort: 9100 + +# ServiceMonitor для Prometheus Operator (если используется) +serviceMonitor: + enabled: true + interval: 30s + scrapeTimeout: 10s diff --git a/monitoring/helm-values/prometheus-values.yaml b/monitoring/helm-values/prometheus-values.yaml new file mode 100644 index 000000000..1396d10e0 --- /dev/null +++ b/monitoring/helm-values/prometheus-values.yaml @@ -0,0 +1,86 @@ +# ============================================================ +# Настройки Prometheus для дипломного проекта +# Файл: monitoring/helm-values/prometheus-values.yaml +# ============================================================ + +# Общие настройки +fullnameOverride: prometheus + +# Ресурсы (экономия для купона) +resources: + requests: + memory: 256Mi + cpu: 100m + limits: + memory: 512Mi + cpu: 200m + +# Хранение данных (минимальное для диплома) +server: + persistentVolume: + enabled: true + size: 5Gi + storageClass: "network-hdd" # Экономия: HDD вместо SSD + + # Увеличим время хранения метрик для демонстрации + retention: 15d + + # Дополнительные флаги для экономии ресурсов + extraArgs: + storage.tsdb.min-block-duration: 2h + storage.tsdb.max-block-duration: 2h + + # ServiceMonitor для сбора метрик самого Prometheus + serviceMonitor: + selfMonitor: true + +# Service для доступа к Prometheus UI +service: + type: ClusterIP + port: 9090 + +# Ingress (опционально, раскомментируйте если нужен доступ извне) +# ingress: +# enabled: true +# className: nginx +# hosts: +# - prometheus.diploma.local +# paths: +# - path: / +# pathType: Prefix + +# Настройка scrape-конфигов для сбора метрик кластера +serverFiles: + prometheus.yml: + scrape_configs: + # Сбор метрик с нод (node-exporter) + - job_name: 'kubernetes-nodes' + kubernetes_sd_configs: + - role: node + relabel_configs: + - action: replace + source_labels: [__meta_kubernetes_node_label_kubernetes_io_hostname] + target_label: instance + - action: replace + source_labels: [__meta_kubernetes_node_address_InternalIP] + target_label: __address__ + regex: (.*) + replacement: ${1}:9100 + + # Сбор метрик с подов (если есть annotations) + - job_name: 'kubernetes-pods' + kubernetes_sd_configs: + - role: pod + relabel_configs: + - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape] + action: keep + regex: true + - source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_path] + action: replace + target_label: __metrics_path__ + regex: (.+) + - source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port] + action: replace + regex: ([^:]+)(?::\d+)?;(\d+) + replacement: $1:$2 + target_label: __address__ diff --git a/monitoring/img/img1.png b/monitoring/img/img1.png new file mode 100644 index 000000000..e7f4bfd07 Binary files /dev/null and b/monitoring/img/img1.png differ diff --git a/monitoring/img/img10.png b/monitoring/img/img10.png new file mode 100644 index 000000000..7f738b09f Binary files /dev/null and b/monitoring/img/img10.png differ diff --git a/monitoring/img/img11.png b/monitoring/img/img11.png new file mode 100644 index 000000000..10a6bb52c Binary files /dev/null and b/monitoring/img/img11.png differ diff --git a/monitoring/img/img12.png b/monitoring/img/img12.png new file mode 100644 index 000000000..f548312de Binary files /dev/null and b/monitoring/img/img12.png differ diff --git a/monitoring/img/img13.png b/monitoring/img/img13.png new file mode 100644 index 000000000..c91b1be4a Binary files /dev/null and b/monitoring/img/img13.png differ diff --git a/monitoring/img/img2.png b/monitoring/img/img2.png new file mode 100644 index 000000000..b5f389230 Binary files /dev/null and b/monitoring/img/img2.png differ diff --git a/monitoring/img/img3.png b/monitoring/img/img3.png new file mode 100644 index 000000000..eb00a3416 Binary files /dev/null and b/monitoring/img/img3.png differ diff --git a/monitoring/img/img4.png b/monitoring/img/img4.png new file mode 100644 index 000000000..e8947c797 Binary files /dev/null and b/monitoring/img/img4.png differ diff --git a/monitoring/img/img5.png b/monitoring/img/img5.png new file mode 100644 index 000000000..ee90c20d5 Binary files /dev/null and b/monitoring/img/img5.png differ diff --git a/monitoring/img/img6.png b/monitoring/img/img6.png new file mode 100644 index 000000000..0b42a018a Binary files /dev/null and b/monitoring/img/img6.png differ diff --git a/monitoring/img/img7.png b/monitoring/img/img7.png new file mode 100644 index 000000000..7b9f83066 Binary files /dev/null and b/monitoring/img/img7.png differ diff --git a/monitoring/img/img8.png b/monitoring/img/img8.png new file mode 100644 index 000000000..5e883c779 Binary files /dev/null and b/monitoring/img/img8.png differ diff --git a/monitoring/img/img9.png b/monitoring/img/img9.png new file mode 100644 index 000000000..2b2eca962 Binary files /dev/null and b/monitoring/img/img9.png differ diff --git a/monitoring/manifests/namespace.yaml b/monitoring/manifests/namespace.yaml new file mode 100644 index 000000000..f72baebaa --- /dev/null +++ b/monitoring/manifests/namespace.yaml @@ -0,0 +1,15 @@ +# ============================================================ +# Пространство имён для системы мониторинга +# Файл: monitoring/manifests/namespace.yaml +# ============================================================ + +apiVersion: v1 +kind: Namespace +metadata: + name: monitoring + labels: + app: monitoring + project: diploma + environment: dev + annotations: + description: "Namespace for Prometheus, Grafana, Alertmanager monitoring stack" diff --git a/registry/.gitignore b/registry/.gitignore new file mode 100644 index 000000000..01e059ec4 --- /dev/null +++ b/registry/.gitignore @@ -0,0 +1,9 @@ +.terraform/ +*.tfstate +*.tfstate.* +*.tfplan +*.tfvars +!*.tfvars.example +.terraform.lock.hcl +*.json +*.key \ No newline at end of file diff --git a/registry/main.tf b/registry/main.tf new file mode 100644 index 000000000..dbd3e322c --- /dev/null +++ b/registry/main.tf @@ -0,0 +1,34 @@ +# ============================================================ +# 🗄️ Yandex Container Registry для тестового приложения +# ============================================================ + +resource "yandex_container_registry" "diploma" { + name = var.registry_name + folder_id = var.yandex_folder_id + + labels = { + project = "diploma" + environment = "dev" + managed_by = "terraform" + } +} + +# 🔐 IAM: права на pull для узлов кластера +#resource "yandex_container_registry_iam_binding" "diploma_puller" { +# registry_id = yandex_container_registry.diploma.id +# role = "container-registry.images.puller" +# +# members = [ +# "serviceAccount:${var.k8s_node_service_account_id}", +# ] +#} + +# 🔐 IAM: права на push для управления +#resource "yandex_container_registry_iam_binding" "diploma_pusher" { +# registry_id = yandex_container_registry.diploma.id +# role = "container-registry.images.pusher" +# +# members = [ +# "serviceAccount:${var.k8s_service_account_id}", +# ] +#} \ No newline at end of file diff --git a/registry/outputs.tf b/registry/outputs.tf new file mode 100644 index 000000000..ba2ec871b --- /dev/null +++ b/registry/outputs.tf @@ -0,0 +1,24 @@ +output "registry_id" { + description = "ID созданного реестра" + value = yandex_container_registry.diploma.id +} + +output "registry_name" { + description = "Имя реестра" + value = yandex_container_registry.diploma.name +} + +output "registry_endpoint" { + description = "Endpoint для docker login" + value = "cr.yandex" +} + +output "image_name_template" { + description = "Шаблон имени образа для push" + value = "cr.yandex/${yandex_container_registry.diploma.id}/diploma-test-app:TAG" +} + +output "docker_login_command" { + description = "Команда для авторизации в реестре" + value = "yc container registry configure --id ${yandex_container_registry.diploma.id}" +} \ No newline at end of file diff --git a/registry/providers.tf b/registry/providers.tf new file mode 100644 index 000000000..c397bd17a --- /dev/null +++ b/registry/providers.tf @@ -0,0 +1,37 @@ +# ============================================================ +# 🔄 Провайдер + Backend для Container Registry +# Backend указывает на bucket из bootstrap этапа +# ============================================================ + +terraform { + required_version = ">= 1.6" + + required_providers { + yandex = { + source = "yandex-cloud/yandex" + version = "~> 0.192" + } + } + + # 🗄️ Backend: S3 bucket в Yandex Object Storage + backend "s3" { + # Эти значения передаются через -backend-config при инициализации + + endpoint = "https://storage.yandexcloud.net" # ✅ С пробелами, как в infrastructure/ + region = "ru-central1" + key = "terraform/registry.tfstate" + + # 🔧 Обход валидаций для Yandex Cloud + skip_region_validation = true + skip_credentials_validation = true + skip_requesting_account_id = true + skip_s3_checksum = true + } +} + +provider "yandex" { + service_account_key_file = var.yandex_key_file + cloud_id = var.yandex_cloud_id + folder_id = var.yandex_folder_id + zone = var.yandex_zone +} diff --git a/registry/variables.tf b/registry/variables.tf new file mode 100644 index 000000000..e8caf0291 --- /dev/null +++ b/registry/variables.tf @@ -0,0 +1,41 @@ +variable "yandex_cloud_id" { + type = string + sensitive = true +} + +variable "yandex_folder_id" { + type = string + sensitive = true +} + +variable "yandex_key_file" { + type = string + default = "../kubernetes-kubeadm/terraform-sa-key.json" +} + +variable "yandex_zone" { + type = string + default = "ru-central1-a" +} + +variable "registry_name" { + type = string + default = "diploma-container-registry" +} + +# ❌ УДАЛИТЕ или закомментируйте этот блок: +# variable "registry_description" { +# type = string +# default = "Container registry for diploma project test application" +# } + +# === Service accounts из kubernetes этапа === +variable "k8s_service_account_id" { + type = string + sensitive = true +} + +variable "k8s_node_service_account_id" { + type = string + sensitive = true +} \ No newline at end of file