- Пример файла ansible/playbook.yml
- Пример файла ansible/hosts
- Пример файла ansible/roles/infrastructure/defaults/main.yml
- Пример файла ansible/roles/infrastructure/tasks/main.yml
- Пример файла terraform/main.tf
- Пример файла terraform/.gitignore
Ниже приведён пример создания изолированной инфраструктуры веб-приложения. Доступ напрямую к веб-серверам запрещен. HTTP доступ предоставляется через балансировщик нагрузки, а ssh — через сервер-бастион.
Пример файла ansible/playbook.yml
- name: Setup Infrastructure
hosts: localhost
connection: local
vars:
terraform_dir: "{{ playbook_dir }}/../terraform"
# Пример использования своей роли
# Структура директорий роли описана здесь https://docs.ansible.com/ansible/latest/user_guide/playbooks_reuse_roles.html
roles:
- infrastructure
Пример файла ansible/hosts
[bastions]
bastion1 ansible_host=161.35.157.17 ansible_user=root
[webservers]
security-web-1 ansible_host=192.168.10.2 ansible_user=root
security-web-2 ansible_host=192.168.10.4 ansible_user=root
Пример файла ansible/roles/infrastructure/defaults/main.yml
# стандартные значения переменных, если для роли не задали переменные
pvt_key: ~/.ssh/id_rsa
infra_state: "present"
Пример файла ansible/roles/infrastructure/tasks/main.yml
- name: Apply terraform infrastructure (webservers, loadbalancer, domain)
community.general.terraform:
project_path: "{{ terraform_dir }}"
variables:
do_token: "{{ do_token }}"
pvt_key: "{{ pvt_key }}"
force_init: yes
state: "{{ infra_state }}"
# Используются outputs из terraform для получения данных о серверах (ip-адреса)
register: infra
- name: Generate hosts
template:
src: templates/hosts.j2
dest: hosts
when: infra_state == "present"
- name: Generate ssh_config
template:
src: templates/ssh_config.j2
dest: ../ssh_config
when: infra_state == "present"
Пример файла terraform/main.tf
// Для удобства всё находится в одном файле
// При необходимости разделяется на отдельные .tf файлы
// https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs
terraform {
required_providers {
digitalocean = {
source = "digitalocean/digitalocean"
version = "1.22.2"
}
}
}
// Токен DO и путь к приватному ключу, будут передаваться через CLI
variable "do_token" {}
variable "pvt_key" {}
provider "digitalocean" {
// Использование переменной (токен доступа к DO)
// https://www.terraform.io/docs/language/values/variables.html
token = var.do_token
}
// Ключ можно либо получить созданный, либо создать новый
// resource "digitalocean_ssh_key" "default" {
// name = "Terraform Homework"
// public_key = file("~/.ssh/id_rsa.pub")
// }
// Используется data source - ресурс не создаётся. Terraform запрашивает информацию о ресурсе
// https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs/data-sources/droplet
data "digitalocean_ssh_key" "example" {
// Имя под которым ключ сохранён в DO
// https://cloud.digitalocean.com/account/security
name = "key"
}
output "webservers" {
value = digitalocean_droplet.web
}
output "bastion" {
value = digitalocean_droplet.bastion
}
// Создаём виртуальную сеть
// https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs/resources/vpc
resource "digitalocean_vpc" "example" {
name = "security-network-example"
region = "ams3"
ip_range = "192.168.10.0/24"
}
// Создание балансировщика нагрузки
// https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs/resources/loadbalancer
resource "digitalocean_loadbalancer" "example" {
name = "security-loadbalancer-example"
region = "ams3"
vpc_uuid = digitalocean_vpc.example.id
sticky_sessions {
type = "cookies"
cookie_name = "lb"
cookie_ttl_seconds = 120
}
dynamic "forwarding_rule" {
for_each = [
{
port = 80
protocol = "http"
}
]
content {
entry_port = forwarding_rule.value["port"]
entry_protocol = forwarding_rule.value["protocol"]
target_port = 8080
target_protocol = "http"
}
}
healthcheck {
port = 8080
protocol = "http"
path = "/"
}
droplet_ids = digitalocean_droplet.web.*.id
}
// Создаём дроплет
// https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs/resources/droplet
resource "digitalocean_droplet" "web" {
count = 2
image = "docker-20-04"
name = "security-web-${count.index + 1}"
region = "ams3"
size = "s-1vcpu-1gb"
private_networking = true
vpc_uuid = digitalocean_vpc.example.id
// Добавление приватного ключа на создаваемый сервер
// Обращение к datasource выполняется через data.
ssh_keys = [
data.digitalocean_ssh_key.example.id
]
}
// Создаём файрволл
// https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs/resources/firewall
resource "digitalocean_firewall" "web" {
name = "web-only-lb-and-ssh"
droplet_ids = digitalocean_droplet.web.*.id
inbound_rule {
protocol = "tcp"
port_range = "22"
source_droplet_ids = [digitalocean_droplet.bastion.id]
}
inbound_rule {
protocol = "tcp"
port_range = "8080"
source_load_balancer_uids = [digitalocean_loadbalancer.example.id]
}
inbound_rule {
protocol = "icmp"
source_addresses = ["0.0.0.0/0", "::/0"]
}
outbound_rule {
protocol = "tcp"
port_range = "80"
destination_addresses = ["0.0.0.0/0", "::/0"]
}
outbound_rule {
protocol = "tcp"
port_range = "53"
destination_addresses = ["0.0.0.0/0", "::/0"]
}
outbound_rule {
protocol = "udp"
port_range = "53"
destination_addresses = ["0.0.0.0/0", "::/0"]
}
outbound_rule {
protocol = "tcp"
port_range = "443"
destination_addresses = ["0.0.0.0/0", "::/0"]
}
}
resource "digitalocean_droplet" "bastion" {
image = "ubuntu-20-04-x64"
name = "security-bastion"
region = "ams3"
size = "s-1vcpu-1gb"
private_networking = true
vpc_uuid = digitalocean_vpc.example.id
ssh_keys = [
data.digitalocean_ssh_key.example.id
]
}
resource "digitalocean_firewall" "bastion" {
name = "only-lb-and-ssh"
droplet_ids = digitalocean_droplet.web.*.id
inbound_rule {
protocol = "tcp"
port_range = "22"
source_addresses = ["0.0.0.0/0", "::/0"]
}
outbound_rule {
protocol = "tcp"
port_range = "1-65535"
}
outbound_rule {
protocol = "udp"
port_range = "1-65535"
}
outbound_rule {
protocol = "icmp"
port_range = "1-65535"
}
}
// Создание домена
// https://registry.terraform.io/providers/digitalocean/digitalocean/latest/docs/resources/domain
resource "digitalocean_domain" "example" {
name = "hexlet.devops-baby.club"
ip_address = digitalocean_loadbalancer.example.ip
}
Пример файла terraform/.gitignore
.terraform*
*.backup
terraform.tfstate
Самостоятельная работа
Цель этого задания — создать простую изолированную инфраструктуру веб-приложения.
Доступ напрямую к веб-серверам будет запрещен. HTTP доступ предоставляется через балансировщик нагрузки, а ssh — через сервер-бастион. Сами приложения могут общаться со внешним миром, например чтобы что-то сделать на внешних сервисах. Бастион выступает проводником между инфраструктурой приложения и внешним миром. По сути это обычный сервер с самой минимальной конфигурацией и на нём ничего нет кроме ssh. Чтобы что-то выполнить на веб-серверах, то сперва подключаются к бастиону, а с него на сервера внутри сети.
|
https
|
v
+--------------------+
| Load Balancer |
+----------------------------------------------------+
| | | |
| +--------------------+ |
| | |
| | |
| http +---------+ |
| | | | |
| | | | |
| +-------SSH--------| Bastion |<--------SSH-------
| | | | |
| | | | |
| | +---------+ |
| +----------+----------+ |
| | | |
| v v |
| +-------+ +-------+ |
| | web | | web | |
| +---+---+ +---+---+ |
+----------------------------------------------------+
Для выполнения деплоя через бастион необходимо подготовить конфигурацию SSH. Использование другого конфига для SSH выполняется следующим образом -
ssh -F /path/to/config
Для того чтобы Ansible использовал дополнительные флаги, используется флаг ssh-extra-args
. Пример:
ansible -i hosts webservers -m ping --ssh-extra-args "-F /path/to/file"
SSH позволяет проксировать запросы с одного сервера на другой. Таким образом мы можем подключиться к веб-серверам по приватным IP адресам. Пример такой конфигурации и его использования
Host bastion
Hostname 188.166.54.71 // публичный IP-адрес бастиона
User root
Host 192.168.10.3 // IP-адрес полученный из созданной VPC
ProxyJump bastion
User root
Host 192.168.10.4
ProxyJump bastion
User root
ssh -F /path/to/config 192.168.10.4
-
С помощью Terraform опишите инфраструктуру, которая нарисована на диаграмме. Должны быть выполнены следующие требования:
-
Бастион — сервер с минимальной конфигурацией.
-
Веб-сервера — серверы на которых будет работать приложение. Могут изначально содержать Docker
-
Балансировщик — принимает запросы по HTTP и HTTPS и перенаправляет запросы на веб-сервера
-
Бастион, балансировщик нагрузки, веб-серверы находятся внутри одной приватной сети (VPC)
-
Внутри приватной сети у фаервола веб-серверов внутри сети открыты все порты на входящие и исходящие соединения, а также по протоколу ICMP
-
Правила работы с исходящими запросами (outbound rule): должна быть открыт доступ по ICMP. Остальные правила настраиваются по необходимости, если они требуются для работы приложения - например 53 порт (DNS), 80, 443 (для скачивания образов, обновлений кеша и тд)
-
На фаерволе бастиона открыт только ICMP и порт для ssh (22)
-
-
Подключитесь по ssh к бастиону и зайди на любой веб-сервер. Для того чтобы использовать приватный ключ на удаленном сервере (для дальнейшего подключения) ssh выполняют с опцией -A (ssh agent forwarding):
ssh -A username@remote_host
-
Выполните деплой приложения
-
Откройте приложение по IP адресу веб-сервера — приложение должно быть недоступно. Доступ по HTTP предоставляется только с балансировщика
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты