Skip to content

Otus-DevOps-2021-05/Deron-D_infra

Repository files navigation

Лекция №5: Знакомство с облачной инфраструктурой Yandex.Cloud

cloud-bastion

Знакомство с облачной инфраструктурой

Задание:

Запуск VM в Yandex Cloud, управление правилами фаервола, настройка SSH подключения, настройка SSH подключения через Bastion Host, настройка VPN сервера и VPN-подключения.

Цель: В данном дз студент создаст виртуальные машины в Yandex.Cloud. Настроит bastion host и ssh. В данном задании тренируются навыки: создания виртуальных машин, настройки bastion host, ssh

Все действия описаны в методическом указании.

Критерии оценки: 0 б. - задание не выполнено 1 б. - задание выполнено 2 б. - выполнены все дополнительные задания

bastion_IP = 84.252.136.193 someinternalhost_IP = 10.129.0.18

Выполнено:

  1. Создаем инстансы VM bastion и someinternalhost через веб-морду Yandex.Cloud

  2. Генерим пару ключей

ssh-keygen -t rsa -f ~/.ssh/appuser -C appuser -P ""
  1. Проверяем подключение по полученному внешнему адресу
ssh -i ~/.ssh/appuser [email protected]
The authenticity of host '84.252.136.193 (84.252.136.193)' can't be established.
ECDSA key fingerprint is SHA256:mbIKmLTZYygwUXSfTBJ8E017RPU9kNESKHYfdoDWbXY.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '84.252.136.193' (ECDSA) to the list of known hosts.
Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-42-generic x86_64)
 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage
The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.
dpp@h470m ~/otus-devops/Deron-D_infra $ ssh -i ~/.ssh/appuser [email protected]
Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-42-generic x86_64)
 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage
Last login: Sun Jun 20 13:12:26 2021 from 82.194.224.170
appuser@bastion:~$ ssh 10.129.0.18
The authenticity of host '10.129.0.18 (10.129.0.18)' can't be established.
ECDSA key fingerprint is SHA256:WsrgIfB+b7qerWOk1tNLqeyGmKoBfKdWkdVJKVzo6u8.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.129.0.18' (ECDSA) to the list of known hosts.
[email protected]: Permission denied (publickey).
  1. Используем Bastion host для прямого подключения к инстансам внутренней сети:
  • Настроим SSH Forwarding на нашей локальной машине:
 ~/otus-devops/Deron-D_infra $ eval $(ssh-agent -s)
Agent pid 1739595
  • Добавим приватный ключ в ssh агент авторизации:
~/otus-devops/Deron-D_infra $ ssh-add ~/.ssh/appuser
Identity added: /home/dpp/.ssh/appuser (appuser)
  • Проверяем прямое подключение:
ssh -i ~/.ssh/appuser -A [email protected]
Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-42-generic x86_64)
 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage
Last login: Sun Jun 20 13:22:54 2021 from 82.194.224.170
appuser@bastion:~$ ssh 10.129.0.18
Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-42-generic x86_64)
 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage
The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.
appuser@someinternalhost:~$ uname -n
someinternalhost
appuser@someinternalhost:~$ uname -a
Linux someinternalhost 5.4.0-42-generic #46-Ubuntu SMP Fri Jul 10 00:24:02 UTC 2020 x86_64 x86_64 x86_64 GNU/Linux
appuser@someinternalhost:~$ ip a show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether d0:0d:e1:f3:67:f3 brd ff:ff:ff:ff:ff:ff
    inet 10.129.0.18/24 brd 10.129.0.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::d20d:e1ff:fef3:67f3/64 scope link
       valid_lft forever preferred_lft forever
  • Проверим отсутствие каких-либо приватных ключей на bastion машине:
appuser@bastion:~$ ls -la ~/.ssh/
total 16
drwx------ 2 appuser appuser 4096 Jun 20 13:19 .
drwxr-xr-x 4 appuser appuser 4096 Jun 20 13:12 ..
-rw------- 1 appuser appuser  561 Jun 20 13:11 authorized_keys
-rw-r--r-- 1 appuser appuser  222 Jun 20 13:19 known_hosts
  • Самостоятельное задание. Исследовать способ подключения к someinternalhost в одну команду из вашего рабочего устройства:

Добавим в ~/.ssh/config содержимое:

dpp@h470m ~/otus-devops/Deron-D_infra $ cat ~/.ssh/config
Host 84.252.136.193
  User appuser
  IdentityFile /home/dpp/.ssh/appuser
Host 10.129.0.18
  User appuser
  ProxyCommand ssh -W %h:%p 84.252.136.193
  IdentityFile /home/dpp/.ssh/appuser

Проверяем работоспособность найденного решения:

~/otus-devops/Deron-D_infra $ ssh 10.129.0.18
The authenticity of host '10.129.0.18 (<no hostip for proxy command>)' can't be established.
ECDSA key fingerprint is SHA256:WsrgIfB+b7qerWOk1tNLqeyGmKoBfKdWkdVJKVzo6u8.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.129.0.18' (ECDSA) to the list of known hosts.
Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-42-generic x86_64)
 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings
Last login: Sun Jun 20 13:24:16 2021 from 10.129.0.30
  • Дополнительное задание:

На локальной машине правим /etc/hosts

sudo bash -c 'echo "10.129.0.18 someinternalhost" >> /etc/hosts'

Добавим в ~/.ssh/config содержимое:

Host someinternalhost
  User appuser
  ProxyCommand ssh -W %h:%p 84.252.136.193
  IdentityFile /home/dpp/.ssh/appuser

Проверяем:

dpp@h470m ~/otus-devops/Deron-D_infra $ ssh someinternalhost
The authenticity of host 'someinternalhost (<no hostip for proxy command>)' can't be established.
ECDSA key fingerprint is SHA256:WsrgIfB+b7qerWOk1tNLqeyGmKoBfKdWkdVJKVzo6u8.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added 'someinternalhost' (ECDSA) to the list of known hosts.
Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 5.4.0-42-generic x86_64)
 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage
Failed to connect to https://changelogs.ubuntu.com/meta-release-lts. Check your Internet connection or proxy settings
Last login: Sun Jun 20 14:03:20 2021 from 10.129.0.30
  • Создаем VPN-сервер для серверов Yandex.Cloud:

Создан скрипт установки VPN-сервера (setupvpn.sh)[./setupvpn.sh]

Веб-интерфейс VPN-сервера Pritunl

Полезное:

Лекция №6: Деплой тестового приложения

cloud-testapp

Деплой тестового приложения

Задание:

Практика управления ресурсами yandex cloud через yc.

Цель: В данном дз произведет ручной деплой тестового приложения. Напишет bash скрипт для автоматизации задач настройки VM и деплоя приложения. В данном задании тренируются навыки: деплоя приложения на сервер, написания bash скриптов.

Ручной деплой тестового приложения. Написание bash скриптов для автоматизации задач настройки VM и деплоя приложения. Все действия описаны в методическом указании.

Критерии оценки: 0 б. - задание не выполнено 1 б. - задание выполнено 2 б. - выполнены все дополнительные задания

testapp_IP = 217.28.229.75 testapp_port = 9292

Выполнено:

  • Установлен YC CLI:
curl https://storage.yandexcloud.net/yandexcloud-yc/install.sh | bash
yc compute instance create \
 --name reddit-app \
 --hostname reddit-app \
 --memory=4 \
 --create-boot-disk image-folder-id=standard-images,image-family=ubuntu-1604-lts,size=10GB \
 --network-interface subnet-name=default-ru-central1-a,nat-ip-version=ipv4 \
 --metadata serial-port-enable=1 \
 --ssh-key ~/.ssh/appuser.pub
sudo apt update
sudo apt install -y ruby-full ruby-bundler build-essential
  • Проверен Ruby и Bundler:
$ ruby -v
ruby 2.3.1p112 (2016-04-26) [x86_64-linux-gnu]
$ bundler -v
Bundler version 1.11.2
wget -qO - https://www.mongodb.org/static/pgp/server-4.2.asc | sudo apt-key add -
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu xenial/mongodb-org/4.2 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.2.list
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates
sudo apt-get update
sudo apt-get install -y mongodb-org
sudo systemctl start mongod
sudo systemctl enable mongod
  • Выполнен деплой приложения deploy.sh:
sudo apt-get install -y git
git clone -b monolith https://github.com/express42/reddit.git
cd reddit && bundle install
  • Дополнительное задание:

Для создания инстанса с уже развернутым приложением достаточно запустить:

yc compute instance create \
 --name reddit-app \
 --hostname reddit-app \
 --memory=4 \
 --create-boot-disk image-folder-id=standard-images,image-family=ubuntu-1604-lts,size=10GB \
 --network-interface subnet-name=default-ru-central1-a,nat-ip-version=ipv4 \
 --metadata-from-file user-data=metadata.yaml \
 --metadata serial-port-enable=1

Содержимое metadata.yaml:

#cloud-config
users:
  - default
  - name: yc-user
    groups: sudo
    shell: /bin/bash
    sudo: ['ALL=(ALL) NOPASSWD:ALL']
    ssh-authorized-keys:
      - "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDUSGRF2QvKndsn1hbFv93CgS3/AiwCoETwjHL6Wzkyape+sW5EXKT/MXjCTlBVfqPtKWvY2pqXpEY7oJAOmJJrBvwnuod2SzEEoFncK1YOLXJOhzeXkT1+1cgo27jJYb4TQTWjawCYv48kJnPNwSL/jNLGQSdosfH3POQVWkB3xCjoLZ7/kMqZQbFEvol5BI5T0HM7uKtPJdWUPD0X1Jpu5MgFV6ZmSWWVrGY25nTehs0nTy4AkAv5mp8VJQtzpKu+fennhQdeb+8aGEaZkFNUOGFAf9ph0G4Lq/gks491Un7cL1/HvcRgPvDdqS+ZRKaPopqK/f978VkpzovlZNJWERZyTrzbgkme6x88zv+rWUu3DiWhldGNuBdghA2kOGhSpSX80gLlj8yE3IP8pdveOq10OztLVpy+8j7tSegOdU9QnBNZ/wqgSVa9kWCU/fui4ASDAA4IAWtthUkaqmDdSPM8mPv8KYueR75LOPKMCCclAOz8S8LK1kFRwcJVEs8= appuser"

runcmd:
  - wget https://raw.githubusercontent.com/Otus-DevOps-2021-05/Deron-D_infra/cloud-testapp/bootstrap.sh
  - bash bootstrap.sh

Содержимое bootstrap.sh:

#!/bin/bash
wget -qO - https://www.mongodb.org/static/pgp/server-4.2.asc | sudo apt-key add -
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu xenial/mongodb-org/4.2 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.2.list
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates ruby-full ruby-bundler build-essential mongodb-org git
sudo systemctl --now enable mongod
git clone -b monolith https://github.com/express42/reddit.git
cd reddit && bundle install
puma -d

Полезное:

Лекция №7: Сборка образов VM при помощи Packer

packer-base

Сборка образов VM при помощи Packer

Задание:

Подготовка базового образа VM при помощи Packer.

Цель: В данном дз студент произведет сборку готового образа с уже установленным приложением при помощи Packer. Задеплоит приложение в Compute Engine при помощи ранее подготовленного образа. В данном задании тренируются навыки: работы с Packer, работы с GCP Compute Engine.

Все действия описаны в методическом указании.

Критерии оценки: 0 б. - задание не выполнено 1 б. - задание выполнено 2 б. - выполнены все дополнительные задания


Выполнено:

  1. Установлен Packer:
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
sudo yum -y install packer

2.1 Создан сервисный аккаунт:

SVC_ACCT="svcuser"
FOLDER_ID="b1gu87e4thvariradsue"
yc iam service-account create --name $SVC_ACCT --folder-id $FOLDER_ID

2.2 Делегированы правы сервисному аккаунту для Packer:

$ ACCT_ID=$(yc iam service-account get $SVC_ACCT | \
grep ^id | \
awk '{print $2}')
$ yc resource-manager folder add-access-binding --id $FOLDER_ID \
--role editor \
--service-account-id $ACCT_ID

2.3 Создан service account key file

Deron-D_infra git:(packer-base) ✗  yc iam key create --service-account-id $ACCT_ID --output ~/.yc_keys/key.json
id: aje6jvgee8cm640mh2b0
service_account_id: ajeeg8qoctaevkcq8jmv
created_at: "2021-06-28T13:08:50.312786870Z"
key_algorithm: RSA_2048

Deron-D_infra git:(packer-base) ✗ ll ~/.yc_keys
total 4.0K
-rw-------. 1 dpp dpp 2.4K Jun 28 16:08 key.json
  1. Создан файла-шаблона Packer ubuntu16.json

  2. Созданы скрипты для provisioners install_ruby.sh;install_mongodb.sh

  3. Выполнена проверка на ошибки

packer validate ./ubuntu16.json
  1. Произведен запуск сборки образа
packer build ./ubuntu16.json
  1. Создана ВМ с использованием созданного образа

  2. Выполнено "дожаривание" ВМ для запуска приложения:

sudo apt-get update
sudo apt-get install -y git
git clone -b monolith https://github.com/express42/reddit.git
cd reddit && bundle install
puma -d
  1. Выполнено параметризирование шаблона с применением variables.json.example

  2. Построение bake-образа *

packer build -var-file=./variables.json immutable.json
  • Проверка созданных образов:
packer git:(packer-base) yc compute image list
+----------------------+------------------------+-------------+----------------------+--------+
|          ID          |          NAME          |   FAMILY    |     PRODUCT IDS      | STATUS |
+----------------------+------------------------+-------------+----------------------+--------+
| fd821hvkilmtrb7tbi2n | reddit-base-1624888205 | reddit-base | f2el9g14ih63bjul3ed3 | READY  |
| fd8t49b4simvfj6crpta | reddit-full-1624909929 | reddit-full | f2el9g14ih63bjul3ed3 | READY  |
+----------------------+------------------------+-------------+----------------------+--------+
  1. Автоматизация создания ВМ *

Полезное:

Лекция №8: Знакомство с terraform

terraform-1

Знакомство с terraform

Задание:

Декларативное описание в виде кода инфраструктуры YC, требуемой для запуска тестового приложения, при помощи Terraform.

Цель: В данном дз студент опишет всю инфраструктуру в Yandex Cloud при помощи Terraform. В данном задании тренируются навыки: создания и описания инфраструктуры при помощи Terraform. Принципы и подходы IaC.

Все действия описаны в методическом указании.

Критерии оценки: 0 б. - задание не выполнено 1 б. - задание выполнено 2 б. - выполнены все дополнительные задания


Выполнено:

  1. Установлен terraform 0.12.8 с помощью terraform-switcher
curl -L https://raw.githubusercontent.com/warrensbox/terraform-switcher/release/install.sh | sudo bash

➜  Deron-D_infra git:(terraform-1) ✗ tfswitch
Use the arrow keys to navigate: ↓ ↑ → ←
? Select Terraform version:
  ▸ 0.12.8 *recent

terraform git:(terraform-1) ✗ terraform -v
Terraform v0.12.8
  1. В корне репозитория создали файл .gitignore с содержимым:
*.tfstate
*.tfstate.*.backup
*.tfstate.backup
*.tfvars
.terraform/
  1. Узнаем свои параметры токена, идентификатора облака и каталога:
yc config list
➜  Deron-D_infra git:(terraform-1) ✗ yc config list
token: <OAuth или статический ключ сервисного аккаунта>
cloud-id: <идентификатор облака>
folder-id: <идентификатор каталога>
compute-default-zone: ru-central1-a
  1. Создадим сервисный аккаунт для работы terraform:
FOLDER_ID=$(yc config list | grep folder-id | awk '{print $2}')
SRV_ACC=trfuser
yc iam service-account create --name $SRV_ACC --folder-id $FOLDER_ID
SRV_ACC_ID=$(yc iam service-account get $SRV_ACC | grep ^id | awk '{print $2}')
yc resource-manager folder add-access-binding --id $FOLDER_ID --role editor --service-account-id $SRV_ACC_ID
yc iam key create --service-account-id $SRV_ACC_ID --output ~/.yc_keys/key.json
  1. Cмотрим информацию о имени, семействе и id пользовательских образов своего каталога с помощью команды yc compute image list:
Deron-D_infra git:(terraform-1) yc compute image list
+----------------------+------------------------+-------------+----------------------+--------+
|          ID          |          NAME          |   FAMILY    |     PRODUCT IDS      | STATUS |
+----------------------+------------------------+-------------+----------------------+--------+
| fd821hvkilmtrb7tbi2n | reddit-base-1624888205 | reddit-base | f2el9g14ih63bjul3ed3 | READY  |
| fd8t49b4simvfj6crpta | reddit-full-1624909929 | reddit-full | f2el9g14ih63bjul3ed3 | READY  |
+----------------------+------------------------+-------------+----------------------+--------+
  1. Cмотрим информацию о имени и id сети своего каталога с помощью команды yc vpc network list:
Deron-D_infra git:(terraform-1) yc vpc network list
+----------------------+--------+
|          ID          |  NAME  |
+----------------------+--------+
| enpv6gbrqnhhbp41jurh | my-net |
+----------------------+--------+
  1. Правим main.tf до состояния:
terraform {
  required_version = "0.12.8"
}

provider "yandex" {
  version= "0.35"
  service_account_key_file = pathexpand("~/.yc_keys/key.json")
  folder_id = "b1gu87e4thvariradsue"
  zone = "ru-central1-a"
}

resource "yandex_compute_instance" "app" {
  name = "reddit-app"
  resources {
    cores = 2
    memory = 2
  }
  boot_disk {
    initialize_params {
    # Указать id образа созданного в предыдущем домашнем задании
    image_id = "fd821hvkilmtrb7tbi2n"
    }
  }
  network_interface {
  # Указан id подсети default-ru-central1-a
  subnet_id = "e9b7qomc4stvbnr6ejde"
  nat = true
  }
}
  1. Для того чтобы загрузить провайдер и начать его использовать выполняем следующую команду в директории terraform:
terraform init
  1. Планируем изменения:
terraform plan
...
Plan: 1 to add, 0 to change, 0 to destroy.
------------------------------------------------------------------------
  1. Создаем VM согласно описанию:
➜  terraform git:(terraform-1) terraform apply -auto-approve
yandex_compute_instance.app: Creating...
yandex_compute_instance.app: Still creating... [10s elapsed]
yandex_compute_instance.app: Still creating... [20s elapsed]
yandex_compute_instance.app: Still creating... [30s elapsed]
yandex_compute_instance.app: Still creating... [40s elapsed]
yandex_compute_instance.app: Creation complete after 46s [id=fhm2sg90ppv3l27lhudf]
  1. Смотрим внешний IP адрес созданного инстанса,
terraform git:(terraform-1) ✗ terraform show | grep nat_ip_address
        nat_ip_address = "84.201.158.45"
  1. Пробуем подключиться по SSH:
terraform git:(terraform-1) ✗ ssh [email protected]
[email protected]'s password:
  1. Нужно определить SSH публичный ключ пользователя ubuntu в метаданных нашего инстанса добавив в main.tf:
metadata = {
ssh-keys = "ubuntu:${file("~/.ssh/appuser.pub")}"
}
  1. Проверяем:
➜  terraform git:(terraform-1) ✗ ssh [email protected] -i ~/.ssh/appuser -o StrictHostKeyChecking=no
Warning: Permanently added '178.154.201.37' (ECDSA) to the list of known hosts.
Welcome to Ubuntu 16.04.7 LTS (GNU/Linux 4.4.0-142-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage
  1. Создадим файл outputs.tf для управления выходными переменными с содержимым:
output "external_ip_address_app" {
  value = yandex_compute_instance.app.network_interface.0.nat_ip_address
}
  1. Проверяем работоспособность outputs.tf:
➜  terraform git:(terraform-1) ✗ terraform refresh
yandex_compute_instance.app: Refreshing state... [id=fhm5o0gooknpnp6v5nmk]
Outputs:
external_ip_address_app = 84.201.157.46

➜  terraform git:(terraform-1) ✗ terraform output
external_ip_address_app = 84.201.157.46
  1. Добавляем provisioners в main.tf:
provisioner "file" {
  source = "files/puma.service"
  destination = "/tmp/puma.service"
}

provisioner "remote-exec" {
script = "files/deploy.sh"
}
  1. Создадим файл юнита для провижионинга puma.service

  2. Добавляем секцию для определения паметров подключения привиженеров:

connection {
  type = "ssh"
  host = yandex_compute_instance.app.network_interface.0.nat_ip_address
  user = "ubuntu"
  agent = false
  # путь до приватного ключа
  private_key = file("~/.ssh/appuser")
  }
  1. Проверяем работу провижинеров. Говорим terraform'y пересоздать ресурс VM при следующем применении изменений:
➜  terraform git:(terraform-1) ✗ terraform taint yandex_compute_instance.app
Resource instance yandex_compute_instance.app has been marked as tainted.
  1. Планируем и применяем изменения:
terraform plan
terraform apply --auto-approve
  1. Проверяем результат изменений и работу приложения:
Apply complete! Resources: 1 added, 0 changed, 1 destroyed.
Outputs:
external_ip_address_app = 178.154.206.153
  1. Параметризируем конфигурационные файлы с помощью входных переменных:
  • Создадим для этих целей еще один конфигурационный файл variables.tf
  • Определим соответствующие параметры ресурсов main.tf через переменные:
provider "yandex" {
  service_account_key_file = var.service_account_key_file
  cloud_id = var.cloud_id
  folder_id = var.folder_id
  zone = var.zone
}
boot_disk {
  initialize_params {
    # Указать id образа созданного в предыдущем домашем задании
    image_id = var.image_id
  }
}

network_interface {
  # Указан id подсети default-ru-central1-a
  subnet_id = var.subnet_id
  nat       = true
}

metadata = {
ssh-keys = "ubuntu:${file(var.public_key_path)}"
}
  1. Определим переменные используя специальный файл terraform.tfvars

  2. Форматирование и финальная проверка:

terraform fmt
terraform destroy
terraform plan
terraform apply --auto-approve

Проверка сервиса по адресу: http://178.154.206.153:9292/

Создание HTTP балансировщика **

  1. Создадим файл lb.tf со следующим содержимым:
resource "yandex_lb_target_group" "reddit_lb_target_group" {
  name      = "reddit-app-lb-group"
  region_id = var.region_id

  target {
    subnet_id = var.subnet_id
    address   = yandex_compute_instance.app.network_interface.0.ip_address
  }
}

resource "yandex_lb_network_load_balancer" "load_balancer" {
  name = "reddit-app-lb"

  listener {
    name = "reddit-app-listener"
    port = 80
    target_port = 9292
    external_address_spec {
      ip_version = "ipv4"
    }
  }

  attached_target_group {
    target_group_id = "${yandex_lb_target_group.reddit_lb_target_group.id}"

    healthcheck {
      name = "http"
      http_options {
        port = 9292
        path = "/"
      }
    }
  }
}
  1. Добавляем в outputs.tf переменные адреса балансировщика и проверяем работоспособность решения:
output "loadbalancer_ip_address" {
  value = yandex_lb_network_load_balancer.load_balancer.listener.*.external_address_spec[0].*.address
}
  1. Добавляем в код еще один terraform ресурс для нового инстанса приложения (reddit-app2):
  • main.tf
resource "yandex_compute_instance" "app2" {
  name = "reddit-app2"
  resources {
    cores  = 2
    memory = 2
  }
...
  connection {
    type  = "ssh"
    host  = yandex_compute_instance.app2.network_interface.0.nat_ip_address
    user  = "ubuntu"
    agent = false
    # путь до приватного ключа
    private_key = file("~/.ssh/appuser")
  }
  • lb.tf
target {
  address = yandex_compute_instance.app2.network_interface.0.ip_address
  subnet_id = var.subnet_id
}
  • outputs.tf
output "external_ip_address_app2" {
  value = yandex_compute_instance.app2.network_interface.0.nat_ip_address
}

Проблемы в данной конфигурации:

  • Избыточный код
  • На инстансах нет единого бэкэнда в части БД (mongodb)
  1. Подход с заданием количества инстансов через параметр ресурса count:
  • Добавим в variables.tf
variable count_of_instances {
  description = "Count of instances"
  default     = 1
}
  • В main.tf удалим код для reddit-app2 и добавим:
resource "yandex_compute_instance" "app" {
  name  = "reddit-app-${count.index}"
  count = var.count_of_instances
  resources {
    cores  = 2
    memory = 2
  }
...
connection {
  type  = "ssh"
  host  = self.network_interface.0.nat_ip_address
  user  = "ubuntu"
  agent = false
  # путь до приватного ключа
  private_key = file("~/.ssh/appuser")
}
  • В lb.tf внесем изменения для динамического определения target:
dynamic "target" {
  for_each = yandex_compute_instance.app.*.network_interface.0.ip_address
  content {
    subnet_id = var.subnet_id
    address   = target.value
  }
}

Полезное:

➜  terraform git:(terraform-1) ✗ yc load-balancer network-load-balancer list
+----------------------+---------------+-------------+----------+----------------+------------------------+--------+
|          ID          |     NAME      |  REGION ID  |   TYPE   | LISTENER COUNT | ATTACHED TARGET GROUPS | STATUS |
+----------------------+---------------+-------------+----------+----------------+------------------------+--------+
| b7rul6kjivb12lgkrfud | reddit-app-lb | ru-central1 | EXTERNAL |              1 | enp46hq1qjve9id5v9oo   | ACTIVE |
+----------------------+---------------+-------------+----------+----------------+------------------------+--------+

➜  terraform git:(terraform-1) ✗ yc load-balancer target-group list
+----------------------+---------------------+---------------------+-------------+--------------+
|          ID          |        NAME         |       CREATED       |  REGION ID  | TARGET COUNT |
+----------------------+---------------------+---------------------+-------------+--------------+
| enp46hq1qjve9id5v9oo | reddit-app-lb-group | 2021-07-24 12:42:52 | ru-central1 |            2 |
+----------------------+---------------------+---------------------+-------------+--------------+

Лекция №9: Принципы организации инфраструктурного кода и работа над инфраструктурой в команде на примере Terraform

terraform-2

Создание Terraform модулей для управления компонентами инфраструктуры.

Задание:

Создание Terraform модулей для управления компонентами инфраструктуры.

Цель: В данном дз студент продолжит работать с Terraform. Опишет и произведет настройку нескольких окружений при помощи Terraform. Настроит remote backend. В данном задании тренируются навыки: работы с Terraform, использования внешних хранилищ состояния инфраструктуры.

Описание и настройка инфраструктуры нескольких окружений. Работа с Terraform remote backend.

Критерии оценки: 0 б. - задание не выполнено 1 б. - задание выполнено 2 б. выполнены все дополнительные задания


Выполнено:

  1. Создаем новую ветку в инфраструктурном репозитории и подчищаем результаты заданий со ⭐:
git checkout -b terraform-2
git mv terraform/lb.tf terraform/files/
  1. Зададим IP для инстанса с приложением в виде внешнего ресурса, добавив в main.tf:
resource "yandex_vpc_network" "app-network" {
  name = "reddit-app-network"
}
resource "yandex_vpc_subnet" "app-subnet" {
  name           = "reddit-app-subnet"
  zone           = "ru-central1-a"
  network_id     = "${yandex_vpc_network.app-network.id}"
  v4_cidr_blocks = ["192.168.10.0/24"]
}
  • также добавим в 'main.tf' ссылку на внешний ресурс:
network_interface {
  subnet_id = yandex_vpc_subnet.app-subnet.id
  nat = true
}
  1. Применим изменения
terraform destroy
terraform apply
yandex_vpc_network.app-network: Creating...
yandex_vpc_network.app-network: Creation complete after 5s
yandex_vpc_subnet.app-subnet: Creating...
yandex_vpc_subnet.app-subnet: Creation complete after 5s
yandex_compute_instance.app: Creating...

Видим, что ресурс VM начал создаваться только после завершения создания yandex_vpc_subnet в результате неявной зависимости этих ресурсов.

  1. Создание раздельных образов для инстансов app и db с помощью Packer:

В директории packer, где содержатся ваши шаблоны для билда VM, создадим два новых шаблона db.json и app.json.

В качестве базового шаблона используем уже имеющийся шаблон ubuntu16.json, корректирую только соответствующие секции с наименованиями образов и секциями провизионеров.

  1. Создадим две VM

Разобьем конфиг main.tf на несколько конфигов Создадим файл app.tf, куда вынесем конфигурацию для VM с приложением:

resource "yandex_compute_instance" "app" {
  name = "reddit-app"

  labels = {
    tags = "reddit-app"
  }
  resources {
    cores  = 1
    memory = 2
  }

  boot_disk {
    initialize_params {
      image_id = var.app_disk_image
    }
  }

  network_interface {
    subnet_id = yandex_vpc_subnet.app-subnet.id
    nat = true
  }

  metadata = {
  ssh-keys = "ubuntu:${file(var.public_key_path)}"
  }
}

И создадим файл db.tf, куда вынесем конфигурацию для VM с приложением:

resource "yandex_compute_instance" "db" {
  name = "reddit-db"
  labels = {
    tags = "reddit-db"
  }

  resources {
    cores  = 1
    memory = 2
  }

  boot_disk {
    initialize_params {
      image_id = var.db_disk_image
    }
  }

  network_interface {
    subnet_id = yandex_vpc_subnet.app-subnet.id
    nat = true
  }

  metadata = {
  ssh-keys = "ubuntu:${file(var.public_key_path)}"
  }
}

Не забудем объявить соответствующие переменные для образов приложения и базы данных в variables.tf:

variable app_disk_image {
  description = "Disk image for reddit app"
  default = "reddit-app-base"

variable db_disk_image {
  description = "Disk image for reddit db"
  default = "reddit-db-base"
  }
}

Создадим файл vpc.tf, в который вынесем конфигурацию сети и подсети, которое применимо для всех инстансов нашей сети.

resource "yandex_vpc_network" "app-network" {
  name = "app-network"
}

resource "yandex_vpc_subnet" "app-subnet" {
  name           = "app-subnet"
  zone           = "ru-central1-a"
  network_id     = "${yandex_vpc_network.app-network.id}"
  v4_cidr_blocks = ["192.168.10.0/24"]
}

В итоге, в файле main.tf должно остаться только определение провайдера:

provider "yandex" {
  version                  = 0.35
  service_account_key_file = var.service_account_key_file
  cloud_id                 = var.cloud_id
  folder_id                = var.folder_id
  zone                     = var.zone
}

Не забудем добавить nat адреса инстансов в outputs.tf переменные:

output "external_ip_address_app" {
  value = yandex_compute_instance.app.network_interface.0.nat_ip_address
}
output "external_ip_address_db" {
  value = yandex_compute_instance.db.network_interface.0.nat_ip_address
}

Планируем и применяем изменения одной командой:

terraform apply --auto-approve
...
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Outputs:
external_ip_address_app = 178.154.220.171
external_ip_address_db = 178.154.200.211

Проверим доступность по SSH:

➜  terraform git:(terraform-2) ✗ ssh [email protected] -i ~/.ssh/appuser
➜  terraform git:(terraform-2) ✗ ssh [email protected] -i ~/.ssh/appuser

Удалим созданные ресурсы, используя terraform destroy

terraform destroy --auto-approve
  1. Создание модулей

Внутри директории terraform создадим директорию modules, в которой мы будем определять модули.

Внутри директории modules создадим директорию db со следующими файлами: main.tf

resource "yandex_compute_instance" "db" {
  name = "reddit-db"
  labels = {
    tags = "reddit-db"
  }

  resources {
    cores  = 2
    memory = 2
  }

  boot_disk {
    initialize_params {
      # Указать id образа созданного в предыдущем домашем задании
      image_id = var.db_disk_image
    }
  }

  network_interface {
    subnet_id = var.subnet_id
    nat       = true
  }

  metadata = {
    ssh-keys = "ubuntu:${file(var.public_key_path)}"
  }

  connection {
    type  = "ssh"
    host  = self.network_interface.0.nat_ip_address
    user  = "ubuntu"
    agent = false
    # путь до приватного ключа
    private_key = file(var.private_key_path)
  }

  scheduling_policy {
    preemptible = true
  }

variables.tf

variable public_key_path {
  description = "Path to the public key used for ssh access"
}
variable db_disk_image {
  description = "Disk image for reddit db"
  default     = "reddit-db-base"
}
variable subnet_id {
  description = "Subnets for modules"
}
variable private_key_path {
  description = "path to private key"
}

outputs.tf

output "external_ip_address_db" {
  value = yandex_compute_instance.db.network_interface.0.nat_ip_address
}

Создадим по аналогии для модуля приложения директорию modules\app с содержимым main.tf

resource "yandex_compute_instance" "app" {
  name = "reddit-app"
  labels = {
    tags = "reddit-app"
  }

  resources {
    cores  = 2
    memory = 2
  }

  boot_disk {
    initialize_params {
      image_id = var.app_disk_image
    }
  }

  network_interface {
    subnet_id = var.subnet_id
    nat       = true
  }

  metadata = {
    ssh-keys = "ubuntu:${file(var.public_key_path)}"
  }

  connection {
    type  = "ssh"
    host  = self.network_interface.0.nat_ip_address
    user  = "ubuntu"
    agent = false
    # путь до приватного ключа
    private_key = file(var.private_key_path)
  }

  scheduling_policy {
    preemptible = true
  }

  provisioner "file" {
    source      = "files/puma.service"
    destination = "/tmp/puma.service"
  }

  provisioner "remote-exec" {
    script = "files/deploy.sh"
  }

}

variables.tf

variable public_key_path {
  description = "Path to the public key used for ssh access"
}
variable app_disk_image {
  description = "Disk image for reddit app"
  default     = "reddit-app-base"
}
variable subnet_id {
  description = "Subnets for modules"
}
variable private_key_path {
  description = "path to private key"
}

outputs.tf

output "external_ip_address_app" {
  value = yandex_compute_instance.app.network_interface.0.nat_ip_address
}

В файл main.tf, где у нас определен провайдер вставим секции вызова созданных нами модулей

provider "yandex" {
  version                  = "0.35"
  service_account_key_file = var.service_account_key_file
  cloud_id                 = var.cloud_id
  folder_id                = var.folder_id
  zone                     = var.zone
}

data "yandex_compute_image" "app_image" {
  name = var.app_disk_image
}

data "yandex_compute_image" "db_image" {
  name = var.db_disk_image
}

module "app" {
  source          = "./modules/app"
  public_key_path = var.public_key_path
  private_key_path = var.private_key_path
  app_disk_image  = "${data.yandex_compute_image.app_image.id}"
  subnet_id       = var.subnet_id
}
module "db" {
  source          = "./modules/db"
  public_key_path = var.public_key_path
  private_key_path = var.private_key_path
  db_disk_image   = "${data.yandex_compute_image.db_image.id}"
  subnet_id       = var.subnet_id
}

Для использования модулей нужно сначала их загрузить из указанного источника source:

terraform get

Из папки terraform удаляем уже ненужные файлы app.tf, db.tf, vpc.tf и изменяем outputs.tf:

output "external_ip_address_app" {
  value = module.app.external_ip_address_app
}
output "external_ip_address_db" {
  value = module.db.external_ip_address_db
}

Планируем изменения:

terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.
Plan: 2 to add, 0 to change, 0 to destroy.
------------------------------------------------------------------------
Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.

После применения конфигурации с помощью terraform apply в соответствии с нашей конфигурацией проверяем SSH доступ ко обоим инстансам Проверим доступность по SSH:

➜  terraform git:(terraform-2) ✗ ssh [email protected] -i ~/.ssh/appuser
➜  terraform git:(terraform-2) ✗ ssh [email protected] -i ~/.ssh/appuser
  1. Переиспользование модулей В директории terrafrom создадим две директории: stage и prod. Скопируем файлы main.tf, variables.tf, outputs.tf, terraform.tfvars, key.json из директории terraform в каждую из созданных директорий.

Поменяем пути к модулям в main.tf на ../modules/xxx вместо ./modules/xxx в папках stage и prod.

Проверим правильность настроек инфраструктуры каждого окружения:

cd stage
terraform init
terraform plan
terraform apply --auto-approve
terraform destroy --auto-approve

cd ../prod
terraform init
terraform plan
terraform apply --auto-approve
terraform destroy --auto-approve

Отформатируем конфигурационные файлы, используя команду terraform fmt

  1. Настройка хранения стейт файла в удаленном бекенде. Задание со ⭐
➜  Deron-D_infra git:(terraform-2) ✗ yc iam service-account list
+----------------------+---------+
|          ID          |  NAME   |
+----------------------+---------+
| aje0qs1bcqu8rckr53aq | svcuser |
| aje6upad8qvh1nri7dld | appuser |
| ajee7g8ig96qqk45gufm | trfuser |
+----------------------+---------+

➜  Deron-D_infra git:(terraform-2) ✗ yc iam access-key create --service-account-name trfuser
access_key:
  id: ajehu3smo5o7v4pkc3rm
  service_account_id: ajee7g8ig96qqk45gufm
  created_at: "2021-08-11T19:29:47.457399290Z"
  key_id: access-key
secret: secret-key

Соответственно заносим полученные данные в variables.tf и terraform.tvars

Планируем и вносим изменения в инфраструктуру:

➜  terraform git:(terraform-2) ✗ terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.


------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # yandex_storage_bucket.otus-storage-bucket will be created
  + resource "yandex_storage_bucket" "otus-storage-bucket" {
      + access_key         = "access-key"
      + acl                = "private"
      + bucket             = "otus-bucket"
      + bucket_domain_name = (known after apply)
      + force_destroy      = false
      + id                 = (known after apply)
      + secret_key         = (sensitive value)
      + website_domain     = (known after apply)
      + website_endpoint   = (known after apply)
    }

Plan: 1 to add, 0 to change, 0 to destroy.

➜  terraform git:(terraform-2) ✗ terraform apply -auto-approve
yandex_storage_bucket.otus-storage-bucket: Creating...
yandex_storage_bucket.otus-storage-bucket: Creation complete after 0s [id=otus-bucket]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

В окружениях stage и prod создаем backend.tf:

terraform {
  backend "s3" {
    endpoint   = "storage.yandexcloud.net"
    bucket     = "otus-bucket"
    region     = "ru-central1-a"
    key        = "terraform.tfstate"
    # access_key = var.access_key
    # secret_key = var.secret_key
    access_key = "access-key"
    secret_key = "secret-key"

    skip_region_validation      = true
    skip_credentials_validation = true
   }
}

Проверяем сохранение state VM's для каждого из окружений в bucket 'otus-bucket'

➜  prod git:(terraform-2) ✗ cd ../stage
➜  stage git:(terraform-2) ✗ pwd
/home/dpp/otus-devops/Deron-D_infra/terraform/stage
➜  stage git:(terraform-2) ✗ terraform apply -auto-approve
data.yandex_compute_image.app_image: Refreshing state...
data.yandex_compute_image.db_image: Refreshing state...
module.app.yandex_compute_instance.app: Creating...
module.db.yandex_compute_instance.db: Creating...
module.db.yandex_compute_instance.db: Still creating... [10s elapsed]
module.app.yandex_compute_instance.app: Still creating... [10s elapsed]
module.app.yandex_compute_instance.app: Still creating... [20s elapsed]
module.db.yandex_compute_instance.db: Still creating... [20s elapsed]
module.db.yandex_compute_instance.db: Still creating... [30s elapsed]
module.app.yandex_compute_instance.app: Still creating... [30s elapsed]
module.app.yandex_compute_instance.app: Creation complete after 38s [id=fhmlrq77m98nick4v2g9]
module.db.yandex_compute_instance.db: Still creating... [40s elapsed]
module.db.yandex_compute_instance.db: Creation complete after 40s [id=fhmbi51mj3cg0lnpsa1v]

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

Outputs:

external_ip_address_app = 130.193.50.99
external_ip_address_db = 84.201.132.24

➜  stage git:(terraform-2) ✗ cd ../prod
➜  prod git:(terraform-2) ✗ terraform apply -auto-approve
data.yandex_compute_image.db_image: Refreshing state...
data.yandex_compute_image.app_image: Refreshing state...
module.app.yandex_compute_instance.app: Refreshing state... [id=fhmlrq77m98nick4v2g9]
module.db.yandex_compute_instance.db: Refreshing state... [id=fhmbi51mj3cg0lnpsa1v]

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Outputs:

external_ip_address_app = 130.193.50.99
external_ip_address_db = 84.201.132.24
  1. Настройка provisioner. Задание со ⭐⭐

Добавим переменную на включения/отключения provisioner в variables.tf окружений stage|prod:

variable enable_provision {
  description = "Enable provision"
  default = true
}

Добавляем в 'main.tf' для модулей app и db соответственно следующий код перед секцией connection:

resource "null_resource" "app" {
  count = var.enable_provision ? 1 : 0
  triggers = {
    cluster_instance_ids = yandex_compute_instance.app.id
  }

...
resource "null_resource" "db" {
  count = var.enable_provision ? 1 : 0
  triggers = {
    cluster_instance_ids = yandex_compute_instance.db.id
  }

Добавляем передачу значений переменной enable_provision в секции вызова модуля 'main.tf'

module "db|app" {
  ...
  enable_provision = var.enable_provision
  ...

Полезное:

- [Provisioners Without a Resource](https://www.terraform.io/docs/language/resources/provisioners/null_resource.html)

About

Deron-D Infra repository

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published