Задание:

a.    На виртуальной машине Cloud-ADM создайте скрипт «deploy_project_01.sh».

b.    В качестве домашней директории используйте путь /home/altlinux/Projects/Project_01.

c.    Скрипт должен реализовывать следующий функционал:

  1. Автоматическое развёртывание виртуальных машин в соответствие с топологией (см. Топология Project_01).
  2. Характеристики виртуальных машин: 1vCPU, 1 ГБ ОЗУ, 10 ГБ размер диска.
  3. Образ операционной системы: alt-p11-cloud-x86_64.qcow2.
  4. Для виртуальной машины haproxy01 должен создаваться и ассоциироваться Плавающий-IP.

d.    На виртуальной машине Cloud-ADM создайте скрипт «configure_project_01.sh».

e.    В качестве домашней директории используйте путь /home/altlinux/Projects/Project_01.

f.    Скрипт должен реализовывать следующий функционал:

  1. Установку необходимых компонентов для запуска приложения (см. Приложение Project_01) на виртуальных машинах: game01, game02, game03 в виде Docker-контейнеров.
  2. Образ приложения нужно сделать легковесным через мультистейджинг.
  3. На виртуальной машине haproxy01 настройку распределения входящих запросов через haproxy.
  4. Настройку доступа к просмотру собранной статистики в веб-интерфейсе с виртуальной машины Cloud-ADM по haproxy01.dev.au.team /haproxy?stats
  5. Приложение должно быть доступно  с виртуальной машины Cloud-ADM из веб-браузера по именам: game01.dev.au.team, game02.dev.au.team и game03.dev.au.team на 80 порту.
  6. Приложение должно быть доступно из вне по Плавающему-IP виртуальной машины haproxy01 на порту 443.
  7. С виртуальной машины Cloud-ADM доступ к приложению должен быть по https://game.au.team.
  8. Проблем с сертификатом возникать недолжно.

g.    На виртуальной машине Cloud-ADM создайте скрипт «destroy_project_01.sh».

h.    В качестве домашней директории используйте путь /home/altlinux/Projects/Project_01.

i.    Скрипт должен реализовывать следующий функционал:

  1. Удалять все автоматически созданные ресурсы через скрипт deploy_project_01.sh.
  2. Если скрипт destroy_project_01.sh не реализован, участник не получит баллов за данный пункт задания, а эксперты вручную выполнят удаление ресурсов.

Вариант реализации:

Cloud-ADM:

Все последующие действия выполняются из-под пользователя altlinux (не root)

  • Создаём директорию /home/altlinux/Projects/Project_01:
mkdir -p /home/altlinux/Projects/Project_01
  • Для скрипта deploy_project_01.sh будет использоваться Terraform
  • Создадим директорию terraform в ранее созданной директории для Project_01:
mkdir /home/altlinux/Projects/Project_01/terraform
  • Перейдём в директорию /home/altlinux/Projects/:
cd /home/altlinux/Projects/
  • Создадим файл с именем cloudinit.conf (имя произвольное) и поместим в него следующее содержимое:
    • указав переменные окружения для работы OpenStack CLI с API облака
    • в данном конкретном примере подразумевается что у участника:
      1. Внешний доступ к панели самообслуживания: https://cyberinfra.ssa2026.region:8800
      2. Домен: Competence_SiSA
      3. Проект: Project0
      4. Учётная запись: User0
      5. Пароль от уч.записи: P@ssw0rd
export OS_AUTH_URL=https://cyberinfra.ssa2026.region:5000/v3
export OS_IDENTITY_API_VERSION=3
export OS_AUTH_TYPE=password
export OS_PROJECT_DOMAIN_NAME=Competence_SiSA
export OS_USER_DOMAIN_NAME=Competence_SiSA
export OS_PROJECT_NAME=Project0
export OS_USERNAME=User0
export OS_PASSWORD=P@ssw0rd
  • Применяем переменные окружения указанные в файле
source cloudinit.conf
  • Проверяем возможность взаимодействовать чере openstack-cli с облаком:
    • например, выведем список инстансов командой openstack --insecure server list:

    •  или сетей командой openstack --insecure network list:

  • Для корректной работы с Terraform, необходимо создадать файл конфигурации зеркала
  • Файл должен иметь имя .terraformrc ибыть расположен в домашнем каталоге пользователя
  • Файл ~/.terraformrc должен содержать в себе следующее:
provider_installation {
    network_mirror {
        url = "https://terraform-mirror.mcs.mail.ru"
        include = ["registry.terraform.io/*/*"]
    }
    direct {
        exclude = ["registry.terraform.io/*/*"]
    }
}
  • Перейдём в директорию Project_01/terraform/:
cd Project_01/terraform/
  • Создадим файл provider.tf и опишем параметры для подключения к провайдеру openstack для работы с облаком:
    • в данном конкретном примере подразумевается что у участника:
      1. Внешний доступ к панели самообслуживания: https://cyberinfra.ssa2026.region:8800
      2. Домен: Competence_SiSA
      3. Проект: Project0
      4. Учётная запись: User0
      5. Пароль от уч.записи: P@ssw0rd
terraform {
  required_providers {
    openstack = {
      source  = "terraform-provider-openstack/openstack"
      version = "2.1.0"
    }
  }
}

provider "openstack" {
  auth_url    = "https://cyberinfra.ssa2026.region:5000/v3"
  tenant_name = "Project0"
  user_name   = "User0"
  password    = "P@ssw0rd"
  insecure    = true
}
  • Инициализируем текущий каталог для работы с terraform и провайдером openstack:
terraform init
  • Результат успешной инициализации Terraform провайдера:

  • Чтобы с инстанса Cloud-ADM был доступ по SSH (для Ansible) до всех создаваемых инстансов (средствами Terraform), будет использоваться файл cloud-init.yml
  • Создадим ключевую пару:
ssh-keygen -t rsa
  • Поместим содержимое публичного ключа (id_rsa.pub) в файл cloud-init.yml:
cat ~/.ssh/id_rsa.pub > cloud-init.yml
  • Откроем файл cloud-init.yml на редактирование и добавим в самое начало (до содержимого ключа ssh):
    • таким образом, на создаваемых в дальнейшем интстансах будут:
      • создан пользователь altlinux с паролем P@ssw0rd;
      • задан для суперпользователя root пароль toor;
      • передан публичный SSH-ключ Cloud-ADM.
#cloud-config
chpasswd:
  expire: false
  users:
  - {name: altlinux, password: P@ssw0rd, type: text}
  - {name: root, password: toor, type: text}
  ssh_pwauth: false

users:
  - name: altlinux
    sudo: ALL=(ALL) NOPASSWD:ALL
    groups: wheel
    shell: /bin/bash
    ssh_authorized_keys:
      - <СОДЕРЖИМОЕ_ПУБЛИЧНОГО_КЛЮЧА_SSH>
  • Создадим файл vm-game.tf и опишем конфигурацию для создаваемых инстансов game01, game02 и game03:
    • для минимального написания строк кода (время на выполнения задания не резиновое, хардкод приветствуется, данный модуль не проверяет применение лучших практик для автоматизации): 
      1. используем цикл по счётчику count;
      2. избавляемся от параметризации с использованием переменных (хардкодим все значения в данный файл).
      3. значение для параметра flavor_id можно узнать используя команду openstack --insecure flavor list скопировав ID Типа ВМ с 1024 RAM и 1 VCPUs;
      4. значение для параметра uuid можно узнать используя команду openstack --insecure image list скопировав ID образа alt-p11-cloud-x86_64.qcow2.
resource "openstack_compute_instance_v2" "game" {
  count     = "3"
  name      = "game0${count.index + 1}"
  flavor_id = "03bf1b85-2f5f-4ada-a07b-8b994b6dcb57"
  user_data = file("cloud-init.yml")

  block_device {
    uuid                  = "827e08fa-fd3c-41cd-92ca-845bb5018478"
    source_type           = "image"
    volume_size           = "10"
    boot_index            = 0
    destination_type      = "volume"
    delete_on_termination = true
  }

  network {
    port = openstack_networking_port_v2.port_vm_game[count.index].id
  }
}
  • Создадим файл vm-haproxy01.tf и опишем конфигурацию для создаваемого инстанса haproxy01:
    • значение для параметра flavor_id можно узнать используя команду openstack --insecure flavor list скопировав ID Типа ВМ с 1024 RAM и 1 VCPUs;
    • значение для параметра uuid можно узнать используя команду openstack --insecure image list скопировав ID образа alt-p11-cloud-x86_64.qcow2.
resource "openstack_compute_instance_v2" "haproxy" {
  name            = "haproxy01"
  flavor_id       = "03bf1b85-2f5f-4ada-a07b-8b994b6dcb57"
  user_data       = file("cloud-init.yml")

  block_device {
    uuid                  = "827e08fa-fd3c-41cd-92ca-845bb5018478"
    source_type           = "image"
    volume_size           = "10"
    boot_index            = 0
    destination_type      = "volume"
    delete_on_termination = true
  }

  network {
    port = openstack_networking_port_v2.haproxy.id
  }
}
  • Создадим файл network.tf и опишем конфигурацию сети в соответствие с топологией для Project_01:
    • значение для параметра network_id можно узнать используя команду openstack --insecure network list скопировав ID виртуальной сети с именем cloud-net;
    • значение для параметра subnet_id можно узнать используя команду openstack --insecure subnet list скопировав ID виртуальной подсети 192.168.1.0/24;
resource "openstack_networking_port_v2" "port_vm_game" {
    count          = "3"
    name           = "port_vm_game0${count.index + 1}"
    network_id     = "61845892-f9cc-4fde-962c-34b59425a74d"
    admin_state_up = true

    fixed_ip {
        subnet_id   = "13592ca4-8782-410b-9bcc-90810ccab6fe"
        ip_address  = "192.168.1.10${count.index + 1}"
    }
}

resource "openstack_networking_port_v2" "haproxy" {
    name           = "port_vm_haproxy"
    network_id     = "61845892-f9cc-4fde-962c-34b59425a74d"
    admin_state_up = true

    fixed_ip {
        subnet_id   = "13592ca4-8782-410b-9bcc-90810ccab6fe"
        ip_address  = "192.168.1.100"
    }
}
  • Создадим файл floatingip.tf и опишем конфигурацию Плавающего IP-адреса в соответствие с топологией для Project_01:
resource "openstack_networking_floatingip_v2" "floatingip_haproxy" {
  pool = "public"
}

resource "openstack_networking_floatingip_associate_v2" "association_haproxy" {
  port_id     = openstack_networking_port_v2.haproxy.id
  floating_ip = openstack_networking_floatingip_v2.floatingip_haproxy.address
}
  • Выполняем проверку синтаксиса и структуры файлов конфигурации Terraform:
terraform validate
  • Результат:

  • Перейдём в директорию /home/altlinux/Projects/Project_01:
cd /home/altlinux/Projects/Project_01
  • Создадим файл deploy_project_01.sh и укажем инструкции необходимые для запуска Terraform:
#!/bin/bash

cd /home/$USER/Projects
source cloudinit.conf
cd /home/$USER/Projects/Project_01/terraform
terraform init
terraform apply -auto-approve

sleep 60
echo "done"
  • Выдать права на исполнение для файла deploy_project_01.sh:
chmod +x deploy_project_01.sh
  • Запустить скрипт на исполнение:
./deploy_project_01.sh
  • Результат:

  • Проверить автоматически созданные инстансы в облаке:

  • Проверить Плавающий IP-адрес для инстанса haproxy01:

  • Поскольку IP-адресация задаётся статическая, то для удобства работы редактируем конфигурационный файл /etc/hosts:

  • Создадим директорию ansible в ранее созданной директории для Project_01:
mkdir /home/altlinux/Projects/Project_01/ansible
  • Перейдём в директорию /home/altlinux/Projects/Project_01/ansible:
cd /home/altlinux/Projects/Project_01/ansible
  • Создадим файл конфигурации ansible.cfg поместим в него следующее содержимое:
[defaults]
inventory = ./inventory.yml
host_key_checking = False
callback_enabled = profile_tasks
callback_whitelist = profile_tasks
  • Создадим файл inventory.yml и опишем файл инвентаря для Ansible:
    • описав 2 группы хостов с именами proxys и games:
      • группа proxys содержит в себе инстанс haproxy01;
      • группа games содержит в себе инстансы game01, game02 и game03;
all:
  children:
    proxys:
      hosts:
        haproxy01:

    games:
      hosts:
        game01:
        game02:
        game03:
  • Создадим директорию group_vars где будем создавать файлы с переменными для групп хостов:
mkdir group_vars
  • Создадим файл 'all.yml' в директории group_vars:
---
ansible_python_interpreter: /usr/bin/python3
ansible_ssh_user: altlinux
ansible_ssh_private_key_file: ~/.ssh/id_rsa
  • Проверяем возможность ansible подключиться к инстансам описанным в инвентарном файле:
ansible -m ping all
  • Результат:

  • Скачиваем любым способом файлы https://disk.yandex.ru/d/uhpN6U6UYRK_Nw (у приложения есть README от программиста-разработчика)
  • Распаковываем архив в директорию /home/altlinux/Projects/Project_01/2048-game/:
unzip ~/Project_01.zip -d /home/altlinux/Projects/Project_01/2048-game/
  • В директории /home/altlinux/Projects/Project_01/ansible создаём файл Dockerfile:
    • образ приложения нужно сделать легковесным через мультистейджинг
FROM node:16-alpine AS builder

WORKDIR /2048-game

COPY package*.json ./
RUN npm install --include=dev

COPY . .
RUN npm run build

EXPOSE 8080

FROM nginx:stable-alpine3.19
COPY --from=builder /2048-game/dist /usr/share/nginx/html
  • В директории /home/altlinux/Projects/Project_01/ansible создаём файл games_playbook.yml:
    • таким образом, на группе хостов games (на инстансах: game01, game02 и game03) будет настроено:
      1. Установлены пакеты: docker-engine и docker-buildx;
      2. Запущена и добавлена в автозагрузку служба docker;
      3. Скопированы все файлы с Cloud-ADM на удалённые хосты, необходимые для приложения;
      4. Скопирован с Cloud-ADM на удалённые хосты файл Dockerfile;
      5. На удалённых хостах выполнена локальная сборка образа для приложения;
      6. На удалённых хостав выполнен запуск контейнеров с веб-приложением.
---
- hosts: games
  become: true

  tasks:
    - name: Install docker
      community.general.apt_rpm:
        name: 
          - docker-engine
          - docker-buildx
        state: present
        update_cache: true

    - name: Started and enabled docker
      ansible.builtin.systemd:
        name: docker
        state: started
        enabled: true

    - name: Copying the project files
      ansible.builtin.copy:
        src: ../2048-game/
        dest: "/home/{{ ansible_ssh_user }}/2048-game/"

    - name: Copying the Dockerfile
      ansible.builtin.copy:
        src: ./Dockerfile
        dest: "/home/{{ ansible_ssh_user }}/2048-game/"

    - name: Build docker image
      community.docker.docker_image_build:
        name: "2048-game"
        tag: latest
        path: "/home/{{ ansible_ssh_user }}/2048-game/"
        dockerfile: Dockerfile

    - name: Start docker container
      community.docker.docker_container:
        name: "2048-game"
        image: "2048-game"
        ports: "80:80"
        state: started
        restart: true
  • На текущем этап можно выполнить тестовый запуск созданного playbook-а games_playbook.yml:
ansible-playbook games_playbook.yml
  • Результат:

  • Таким образом при обращении в браузере с Cloud-ADM по описанным ниже url должно открываться веб-приложение:
    • game01.dev.au.team:

    • game02.dev.au.team:

    • game03.dev.au.team:

  • В директории /home/altlinux/Projects/Project_01/ansible создаём директорию files:
mkdir files
  • В директории /home/altlinux/Projects/Project_01/ansible/files создаём конфигурационный файл haproxy.cfg
    • в данном файле стандартное содержимое haproxy.cfg за исключением блока frontend и backend для реализации необходимого функционала
      • для удобства можно найти конфигаруционный файл в сети Интернет;
      • или же выполнить установку пакета haproxy и забрать его из директории /etc/haproxy/haproxy.cfg (т.к. инстанс haproxy01 в любом случае будет удалён).
global
    log /dev/log daemon
    chroot      /var/lib/haproxy
    pidfile     /run/haproxy.pid
    maxconn     4000
    user        _haproxy
    group       _haproxy
    daemon
    stats socket /var/lib/haproxy/stats

defaults
    mode                    http
    log                     global
    option                  httplog
    option                  dontlognull
    option http-server-close
    option forwardfor       except 127.0.0.0/8
    option                  redispatch
    retries                 3
    timeout http-request    10s
    timeout queue           1m
    timeout connect         10s
    timeout client          1m
    timeout server          1m
    timeout http-keep-alive 10s
    timeout check           10s
    maxconn                 3000

frontend http_front
    bind *:80
    bind *:443 ssl crt /var/lib/ssl/game.pem
    default_backend http_back

backend http_back
    balance roundrobin
    server game01 192.168.1.101:80 check
    server game02 192.168.1.102:80 check
    server game03 192.168.1.103:80 check

listen stats
bind :80
bind *:443 ssl crt /var/lib/ssl/game.pem
stats enable
stats uri /
stats refresh 5s
stats realm Haproxy\ Stats
  • Средствами утилиты openssl разворачиваем свой Удостоверяющий Центр сертификатов:
openssl req -x509 -sha256 -days 3653 -newkey rsa:2048 -keyout ca.key -out ca.crt
  • Результат:

  • Добавляем корневой сертификат в хранилище на Cloud-ADM:
sudo cp ca.crt /etc/pki/ca-trust/source/anchors/ && sudo update-ca-trust
  • Перемещаем файлы сертификатов в директорию files:
mv ca.* files/
  • Генерируем ключи, запросы и сертификаты для веб:
    • Ключ:
openssl genrsa -out files/game.key 2048
    • Запрос:
openssl req -key files/game.key -new -out files/game.csr
    • Результат:

  • Файл с расширениями для сертификата:
cat <<EOF > files/game.ext
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
subjectAltName=@alt_names
[alt_names]
DNS.1=game.au.team
IP.1=192.168.1.100
EOF
    • Выпускаем сертификат:
openssl x509 -req -CA files/ca.crt -CAkey files/ca.key -in files/game.csr -out files/game.crt -days 365 -CAcreateserial -extfile files/game.ext
  • Создаём цепочку для haproxy:
cat files/game.key files/game.crt > files/game.pem
  • В директории /home/altlinux/Projects/Project_01/ansible создаём файл proxys_playbook.yml:
    • таким образом, на группе хостов games (на инстансах: game01, game02 и game03) будет настроено:
      1. Установлен пакет haproxy;
      2. Скопирован с Cloud-ADM на удалённый хост файл haproxy.cfg;
      3. Скопирован с CloudpADM на удалённый хост файл game.pem;
      4. Включена и добавлена в автозагрузку служба haproxy.
---
- hosts: proxys
  become: true

  tasks:
    - name: Install HAProxy
      community.general.apt_rpm:
        name: haproxy
        state: present
        update_cache: true
    
    - name: Copy file 'haproxy.cfg'
      ansible.builtin.copy:
        src: files/haproxy.cfg
        dest: /etc/haproxy/haproxy.cfg
      notify:
        - Restarted HAProxy

    - name: Copy certificate for HAProxy
      ansible.builtin.copy:
        src: files/game.pem
        dest: /var/lib/ssl/game.pem
      notify:
        - Restarted HAProxy

    - name: Started and enabled HAProxy
      ansible.builtin.systemd:
        name: haproxy
        state: restarted
        enabled: true

  handlers:
    - name: Restarted HAProxy
      ansible.builtin.systemd:
        name: haproxy
        state: restarted
  • На текущем этап можно выполнить тестовый запуск созданного playbook-а proxys_playbook.yml:
ansible-playbook proxys_playbook.yml
  • Результат:

  • Таким образом при обращении в браузере с Cloud-ADM по описанным ниже url должно открываться веб-приложение:
    • https://game.au.team (в файле /etc/hosts должна быть добавлена запись):

    • haproxy01.dev.au.team /haproxy?stats

    • Плавающий IP-адрес:

  • Перейдём в директорию /home/altlinux/Projects/Project_01:
cd /home/altlinux/Projects/Project_01
  • Создадим файл configure_project_01.sh и укажем инструкции необходимые для запуска Ansible:
#!/bin/bash

export PATH=/home/altlinux/.local/bin:$PATH
cd /home/$USER/Projects/Project_01/ansible
sleep 10
ansible-playbook games_playbook.yml
sleep 5
ansible-playbook proxys_playbook.yml
  • Выдать права на исполнение для файла configure_project_01.sh:
chmod +x configure_project_01.sh
  • Запустить скрипт на исполнение:
./configure_project_01.sh
  • Создадим файл destroy_project_01.sh и укажем инструкции необходимые для запуска Ansible:
#!/bin/bash

cd /home/$USER/Projects
source cloudinit.conf
cd /home/$USER/Projects/Project_01/terraform
terraform destroy -auto-approve
rm -f ~/.ssh/known_hosts

echo "done"
  • Выдать права на исполнение для файла destroy_project_01.sh:
chmod +x destroy_project_01.sh
  • Запустить скрипт на исполнение:
./destroy_project_01.sh
  • Результат:

  • Таким образом, структура для Project01 получилась следующая:

Последнее изменение: вторник, 2 декабря 2025, 14:24