dnvlasov Infra repository
Авторизация придложения Отус на Github. Создание Pull Request в в репозиторий Otus-DevOps-2018-09/students
- Закрепляем знания по GIT, работаем в репозиториях, созданных в рамках ДЗ №1
- Создаем интеграцию с чатом для репозитория и подключение Travis CI
- Клонирование репозитория из организации.
git clone git@github.com:Otus-DevOps-2018-09/dnvlasov_infra.git- Работа с ветками.
- Перейти в директорию репозитория cd dnvlasov_infra.
- Создать ветку необходимую для ДЗ
git checkout -b play-travis- Добавление изменений.
- Создать директорию
".github"скачать внутрь файлPULL_REQUEST_TEMPLATE.md
- Отправка изменений.
- Добавить файл
PULL_REQUEST_TEMPLATE.mdв индекс git'a
git add PULL_REQUEST_TEMPLATE.md - Сделать коммит.
git commit -m 'Add PR template' - Отправить изменения на github
git push --set-upstream origin play-travis- Создание канала в slack.
- Добавление пользователей.
- Интеграция с github.
- Набрать команду в своем slack канале
/github subscribe Otus-DevOps-2018-09/dnvlasov_infra commits:all
- Тестируем интерацию.
- Скачать файл test.py в папку play_travis. Проверить что выдает канал.
- Инструкции по сборке.
- Создать в корне репозитория файл .travis.yml c содержимым:
dist: trusty sudo: required language: bash before_install: - curl https://raw.githubusercontent.com/express42/otus-homeworks/2018-09/run.sh | bash
- Интеграция slack с github.
- Интеграция Slack c Travis CI.
- Хранение секретов.
gem install travis
travis login --com
travis encrypt "devops-team-otus:<токен>#<канала>"\ --add notifications.slack.rooms --com- Создание учетной записи в Google Cloud Platform.
- Создаем новый проект.
- Работа с Google Compute Engine.
- Работа с GCE: Метаданные добавить ключи ssh.
- Генерация пары ключей.
ssh-keygen -t rsa -f ~/.ssh/appuser -C appuser -P ""- Приватный ключ: ~/.ssh/appuser
- Публичный ключ: ~/.ssh/appuser.pub
- Вносим в форму публичный ключ.
- Создаем Экземпляр VM (инстанс)
bastion - Проверяем подключение по полученному внешнему адресу.
ssh -i ~/.ssh/appuser appuser@<внешний IP VM>- Создаем вторую VM без внешней сети
someinternalhost. - Используем Bastion host для прямого подключения к инстансам внутренней сети.
- Настроим SSH Forwarding на вашей локальной машине:
$ ssh-add -L
The agent has no identities- Добавим приватный ключ в ssh агент авторизации:
$ ssh-add ~/.ssh/appuser
Identity added: /Users/otus/.ssh/appuser (/Users/otus/.ssh/appuser)- Используем Bastion host для сквозного подключения
ssh -i ~/.ssh/appuser -A appuser@(bastion_IP)
bastion_IP = 204.155.26.14
someinternalhost_IP = 10.132.0.3
- Создаем VPN-сервер для серверов GCP
- В настройках VM Брандмауэры разрешить трафик
HTTP и HTTPS - На хосте bastion выполняем команды Ссылка на gist
$ cat <<EOF> setupvpn.sh
#!/bin/bash
echo "deb http://repo.mongodb.org/apt/ubuntu xenial/mongodb-org/3.4 multiverse" >
apt/sources.list.d/mongodb-org-3.4.list
echo "deb http://repo.pritunl.com/stable/apt xenial main" > /etc/apt/sources.list
pritunl.list
apt-key adv --keyserver hkp://keyserver.ubuntu.com --recv
0C49F3730359A14518585931BC711F9BA15703C6
apt-key adv --keyserver hkp://keyserver.ubuntu.com --recv
7568D9BB55FF9E5287D586017AE645C0CF8E292A
apt-get --assume-yes update
apt-get --assume-yes upgrade
apt-get --assume-yes install pritunl mongodb-org
systemctl start pritunl mongod
systemctl enable pritunl mongod
EOF- Затем выполняем:
$ sudo bash setupvpn.shВ результате:
- В текущей директории будет создан файл
setupvpn.sh, описывающий установку VPN-сервера - Будет установлена mongodb и VPN-cервер pritunl
- Открываем в браузере ссылку:
https://<адрес bastion VM>/setup - Ошибку SSL пропускаем и доверяем этому сайту
- Cледуем инструкциям на экране (запрашиваемые команды запускать через sudo)
- В конце установки авторизуемся, используя логин/пароль pritunl/pritunl
- Далее добавляем в веб интерфейсе:
- Организацию
- Пользователя test с PIN 6214157507237678334670591556762
- Сервер (затем привязываем его к организации и запускаем)
- Создадим правило для открытия порта VPN, (порт указывается при создании сервера).
- Добавим в инстансе bastion в Теги сети наше новое правило
- Cкачать конфигурационный файл для подключени OpenVPNсервер Pritunl
- Проверяем подключение к VPN.
- Добавим полученный конфигурационный файл *.ovpn в клиент OpenVPN на компьютере
- Проверем подключение к VPN-серверу
- Проверем возможность подключения к someinternalhost с компьютера, после подключения к VPN:
$ ssh -i ~/.ssh/appuser appuser@<внутренний IP someinternalhost>В данном ДЗ мы:
- Установим и настроим gcloud для работы с нашим аккаунтом
- Создадим хост с помощью gcloud
- Установим на нем ruby для работы приложения
- Установим MongoDB и запустим
- Задеплоим тестовое приложение, запустим и проверим его работу.
- Установите Google Cloud SDK
- Создайте переменную окружения для правильного распространения:
export CLOUD_SDK_REPO="cloud-sdk-$(lsb_release -c -s)"
- Добавить Cloud SDK репозиторий в source.list:
echo "deb http://packages.cloud.google.com/apt $CLOUD_SDK_REPO main" | sudo tee -a /etc/apt/sources.list.d/google-cloud-sdk.list
- Импортировать публичные ключи Google Cloud:
curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -- Обновление репозитория и установка Cloud SDK:
sudo apt-get update && sudo apt-get install google-cloud-sdk- Опционально должны установиться пакеты:
- google-cloud-sdk-app-engine-python
- google-cloud-sdk-app-engine-python-extras
- google-cloud-sdk-app-engine-java
- google-cloud-sdk-app-engine-go
- google-cloud-sdk-datalab
- google-cloud-sdk-datastore-emulator
- google-cloud-sdk-pubsub-emulator
- google-cloud-sdk-cbt
- google-cloud-sdk-cloud-build-local
- google-cloud-sdk-bigtable-emulator
- kubectl
- Запуск gcloud консоли
gcloud init
- Создаем новый инстанс.
gcloud compute instances create reddit-app\
--project=infra-<id> \
--boot-disk-size=10GB \
--image-family ubuntu-1604-lts \
--image-project=ubuntu-os-cloud \
--machine-type=g1-small \
--tags puma-server \
--restart-on-failure
- Устанавливаем Ruby
- Подключаемся в к машине по SSH
$ ssh appuser@<instace_public_ip> - Обновляем APT и устанавливаем Ruby и Bundler:
$ sudo apt update
$ sudo apt install -y ruby-full ruby-bundler build-essential - Проверем установку Ruby и Bundler
$ ruby -v
$ bundle -v - Устанавливаем MongoDB
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv EA312927
sudo bash -c 'echo "deb http://repo.mongodb.org/apt/ubuntu xenial/mongodb-org/3.2 multiverse" > /etc/apt/sources.list.d/mongodb-org-3.2.list'- Обновим индекс доступных пакетов и установим нужный пакет:
$ sudo apt update
$ sudo apt install -y mongodb-org- Запускаем MongoDB:
$ sudo systemctl start mongod - Добавляем в автозапуск:
$ sudo systemctl enable mongod- Проверяем работу MongoDB
appuser@reddit-app:~$ sudo systemctl status mongod
mongod.service - High-performance, schema-free document-oriented database
Loaded: loaded (/lib/systemd/system/mongod.service; enabled; vendor preset: enabled)
Active: active (running) since Sat 2018-10-20 19:44:26 UTC; 17h ago
Docs: https://docs.mongodb.org/manual
Main PID: 13731 (mongod)
Tasks: 20
Memory: 32.8M
CPU: 5min 23.343s
CGroup: /system.slice/mongod.service
└─13731 /usr/bin/mongod --quiet --config /etc/mongod.conf
Oct 20 19:44:26 reddit-app systemd[1]: Started High-performance, schema-free document-oriented database.- Деплой приложения.
- Копируем код приложения гаходясь в каталоге /home/appuser
$ git clone -b monolith https://github.com/express42/reddit.git - Переходим в директорию проекта и устанавливаем зависимости приложения
$ cd reddit && bundle install- Деплой приложения
- Запускаем сервер приложения в папке проект
$ puma -d - Проверьте что сервер запустился и на каком порту он слушает:
$ ps aux | grep puma
appuser 9582 0.0 2.4 654684 42988 puma 3.10.0 (tcp://0.0.0.0:9292) [reddit]testapp_IP = 35.236.29.190 testapp_IP = 9292
- Открываем порт в файерволе
- create new firewall rule
- Name: default-puma-server
- Targets tag: puma-server
- Source IP ranges: 0.0.0.0/0
- Specified protocols and ports: 9292
- Проверка работы приложения
http://testapp_IP:testapp_IP/- Дополнительное задание
- создать startup script, который будет запускаться при создании инстанса startup.sh
#! /bin/bash
#install ruby
sudo apt update
sudo apt install -y ruby-full ruby-bundler build-essential
#install mongodb
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv EA312927
sudo bash -c 'echo "deb http://repo.mongodb.org/apt/ubuntu\
xenial/mongodb-org/3.2 multiverse" > /etc/apt/sources.list.d/mongodb-org-3.2.list'
sudo apt update
sudo apt install -y mongodb-org
sudo systemctl start mongod
sudo systemctl enable mongod
#deploy puma
cd /home/appuser
git clone -b monolith https://github.com/express42/reddit.git
cd reddit && bundle install
puma -d- перетать при установке
--metadata-from-file startup-script=startup.sh
- так же можно через http protocols
--metadata startup-script-url=https://github.com/Otus-DevOps-2018-09/dnvlasov_infra/blob/cloud-testapp/startup.sh
- Удалите созданное через веб интерфейс правило для работы приложения default-pumaserver.
- Создайте аналогичное правило из консоли с помощью gcloud.
- Используемую команду gcloud необходимо добавить в описание репозитория (README.md)
gcloud compute firewall-rules create default-puma-server --allow tcp:9292 --source-ranges=0.0.0.0/0 --target-tags=puma-server
- Установка packer
- Создане ADC
$ gcloud auth application-default login
- Создание Pcker template
- mkdir packer && touch packer/ubuntu16.json
{
"builders": [
{
"type": "googlecompute",
"project_id": "infra-219415",
"image_name": "reddit-base-{{timestamp}}",
"image_family": "reddit-base",
"source_image_family": "ubuntu-1604-lts",
"zone": "europe-west1-b",
"ssh_username": "appuser",
"machine_type": "f1-micro"
}
],
"provisioners": [
{
"type": "shell",
"inline":[
"sudo apt update"
]
},
{
"type": "shell",
"script": "scripts/install_ruby.sh",
"execute_command": "sudo {{.Path}}"
},
{
"type": "shell",
"script": "scripts/install_mongodb.sh",
"execute_command": "sudo {{.Path}}"
}
]
}
4. Скрипты для провижининга.
- mkdir packer/scripts && cp config-script/install-* packer/scripts
5. Проверка на ошибки
```bash
$ packer validate ./ubuntu16.json - Packer build
- запустить build образа
$ packer build ubuntu16.json- Проверить созданный образ
- В браузерной консоле появился образ ddit-base-1540144393 e. Деплоим приложение
- Создаем машину из образа reddit-base-1540144393
- Подключаемся по ssh
$ ssh appuser@<instace_public_ip>- Установка зависимостей и запуск приложения.
$ git clone -b monolith https://github.com/express42/reddit.git
$ cd reddit && bundle install
$ puma -d- Проверить что приложение запустилось
$ ps aux | grep puma- Проверка работы приложения
http://instace_public_ip:929211.Самостоятельные задания.
- Необходимо параметризировать созданный шаблон, используя пользовательские переменные
- ID проекта (обязательно)
- source_image_family (обязательно)
- machine_type
- Пользовательские переменные определяются в самом
шаблоне, в файле
variables.jsonзадаются обязательные переменные, либо переопределяются -- variables.json
{
"project_id": "infra-3324235",
"source_image_family": "ubuntu-1604-lts",
"machine_type": "f1-micro",
}-- ubuntu16.json
{
"variables":{
"project_id": "variables.json",
"machine_type": "variables.json",
"source_image_family": "variables.json",
},
"builders": [
{
"type": "googlecompute",
"project_id": "{{user `project_id`}}",
"image_name": "reddit-base-{{timestamp}}",
"image_family": "reddit-base",
"source_image_family": "{{user `source_image_family`}}",
"zone": "europe-west1-b",
"ssh_username": "",
"machine_type": "{{user `machine_type`}}",
}- Стартуем билд командой $ packer build -var-file=variables.json ubuntu16.json
- Исследовать другие опции builder для GCP (ссылка).
- Описание образа
- Размер и тип диска
- Название сети
- Теги
-- variables.json
{
"project_id": "infra-000000",
"source_image_family": "ubuntu-1604-lts",
"machine_type": "f1-micro",
"network": "default",
"tags": "puma-server",
"disk_type": "pd-ssd",
"disk_size": "10",
"image_description": "Canonical, Ubuntu, 16.04 LTS, amd64 xenial image"
}-- ubuntu16.json
{
"variables":{
"project_id": "variables.json",
"machine_type": "variables.json",
"source_image_family": "variables.json",
"networks": "variables.json",
"tags": "variables.json",
"disk_type": "variables.json",
"disk_size": "variables.json",
"image_description": "variables.json"
},
"builders": [
{
"type": "googlecompute",
"project_id": "{{user `project_id`}}",
"image_name": "reddit-base-{{timestamp}}",
"image_family": "reddit-base",
"source_image_family": "{{user `source_image_family`}}",
"zone": "europe-west1-b",
"ssh_username": "",
"machine_type": "{{user `machine_type`}}",
"network": "{{user `network`}}",
"tags": "{{user `tags`}}",
"disk_type": "{{user `disk_type`}}",
"disk_size": "{{user `disk_size`}}",
"image_description": "{{user `image_descriprion`}}"
}
],- Создаем новую ветку terraform-1
git checkout -b terraform-1
- Устанавливаем terraform
wget https://releases.hashicorp.com/terraform/0.11.10/terraform_0.11.10_linux_amd64.zipПаспаковываем файл в папку указаной в PATH в окружении пользователя.
unzip terraform_0.11.10_linux_amd64.zip -d ~/otusВ папке terraform создаем файл main.tf. Это будет главный конфигурационный файл в этом задании, который будет содержать декларативное описание нашей инфраструктуры. В корне репозитория создайте файл .gitignore с содержимым указанным в данном gist.
*.tfstate
*.tfstate.*.backup
*.tfstate.backup
*.tfvars
.terraform/ Это необходимо для того, чтобы не коммитить в репозиторий служебные файлы и директории.
- Provider
Первый делом определим секцию Provider в файле main.tf, которая позволит Terraform управлять ресурсами GCP через API вызовы
provider "google" {
version = "1.4.0"
project =
"steam-strategy-174408"
region = "europe-west1"
}- Terraform init
Провайдеры Terraform являются загружаемыми модулями, начиная с версии 0.10. Для того чтобы загрузить провайдер и начать его использовать выполните следующую команду в директории terraform:
$ terraform init
Должны увидеть сообщение, что провайдер был установлен и инициализация Terraform прошла успешно
Initializing provider plugins...
Terraform has been successfully initialized!- Ресурсная модель
Чтобы запустить VM при помощи terraform нам нужно воспользоваться ресурсом google_compute_instance, который позволяет управлять инстансами VM.
В файле main.tf после определения провайдера, добавьте ресурс для создания инстанса VM в GCP.
resource "google_compute_instance" "app" {
name = "reddit-app"
machine_type = "g1-small"
zone = "europe-west1-b"
# определение загрузочного диска
boot_disk {
initialize_params {
image = "reddit-base-1540144393"
}
}
# определение сетевого интерфейса
network_interface {
# сеть, к которой присоединить данный интерфейс
network = "default"
# использовать ephemeral IP для доступа из Интернет
access_config {}
}
}Перед тем как дать команду terraform'у применить изменения, хорошей практикой является предварительно посмотреть, какие изменения terraform собирается произвести относительно состояния известных ему ресурсов (tfstate файл), и проверить, что мы действительно хотим сделать именно эти изменения. Выполните команду планирования изменений в директории terraform:
$ terraform plan - Планируем изменения
Знак "+" перед наименованием ресурса означает, что ресурс будет добавлен. Далее приведены атрибуты этого ресурса. “” означает, что данные атрибуты еще не известны terraform'у и их значения будут получены во время создания ресурса.
+ google_compute_instance.app
boot_disk.#: "1"
boot_disk.0.auto_delete: "true"
boot_disk.0.device_name: "<computed>"
boot_disk.0.disk_encryption_key_sha256: "<computed>"
boot_disk.0.initialize_params.#: "1"
boot_disk.0.initialize_params.0.image: "reddit-base"
Plan: 1 to add, 0 to change, 0 to destroy.Для того чтобы запустить инстанс VM, описание характеристик которого мы привели в конфигурационном файле main.tf, используем команду:
$ terraform apply при выполнении. Необходимо добавить
-auto-approve=true для отключения этого.
В результате применения команды увидим, какие
изменения были произведены terraform'ом:
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.Результатом выполнения команды также будет
создание файла terraform.tfstate в директории
terraform. Terraform хранит в этом файле состояние
управляемых им ресурсов.
В этом файле внешний IP адрес
созданного инстанса, его можно найти командой.
$ terraform show | grep assigned_nat_ipДобавить в main.tf к
resource "google_compute_instance" "app" {
...
metadata {
ssh-keys = "appuser:${file("~/.ssh/appuser.pub")}"
}
...
}Планируем изменения
terraform planПрименяем изменения.
terraform apply
Выновим интересующую нас информацию - внешний адрес VM - в выходную переменную (output variable) Создаем файл outputs.tf в директории terraform со следующим содержимым.
output "app_external_ip" {
value = "${google_compute_instance.app.network_interface.0.access_config.0.assigned_nat_ip}"
}
Используем команду terraform refresh, чтобы
выходная переменная приняла значение.
Outputs:
app_external_ip = 104.155.68.69Значение выходных переменным можно посмотреть, используя команду terraform output:
$ terraform output
app_external_ip = 104.155.68.69
$ terraform output app_external_ip
104.155.68.69Добавим в main.tf следущий ресурс:
resource "google_compute_firewall" "firewall_puma" {
name = "allow-puma-default"
# Название сети, в которой действует правило
network = "default"
# Какой доступ разрешить
allow {
protocol = "tcp"
ports = ["9292"]
}
# Каким адресам разрешаем доступ
source_ranges = ["0.0.0.0/0"]
# Правило применимо для инстансов с перечисленными тэгами
target_tags = ["reddit-app"]
}
Планируем и применяем изменения
$ terraform plan
$ terraform apply Добавим тег в определении ресурса.
resource "google_compute_instance" "app" {
name = "reddit-app"
machine_type = "g1-small"
zone = "europe-west1-b"
tags = ["reddit-app"]Планируем изменения
$ terraform plan
...
~ google_compute_instance.app
tags.#: "0" => "1"
tags.1799682348: "" => "reddit-app"
Plan: 0 to add, 1 to change, 0 to destroy. Видим, что ресурс виртуальной машины будет изменен.
Применяем изменения
$ terraform apply Provisioners в terraform вызываются в момент создания/удаления ресурса и позволяют выполнять команды на удаленной или локальной машине. Их используют для запуска инструментов управления конфигурацией или начальной настройки системы. Используем провижинеры для деплоя последней версии приложения на созданную VM
Внутрь ресурса, содержащего описание VM, вставьте секцию провижинера типа file, который позволяет копировать содержимое файла на удаленную машину.
provisioner "file" {
source = "files/puma.service"
destination = "/tmp/puma.service"
} Говорим, провижинеру
скопировать локальный файл, располагающийся по
указанному относительному пути (files/puma.service), в указанное место на удаленном хосте.
Создадим директорию files внутри директории terraform и создадим внутри нее файл puma.service
[Unit]
Description=Puma HTTP Server
After=network.target
[Service]
Type=simple
User=appuser
WorkingDirectory=/home/appuser/reddit
ExecStart=/bin/bash -lc 'puma'
Restart=always
[Install]
WantedBy=multi-user.target
Добавим еще один провиженер для запуска скрипта деплоя приложения на создаваемом инстансе. Сразу же после определения провижинера file (провижинеры выполняются по порядку их определения), вставьте секцию провижинера remote-exec
provisioner "remote-exec" {
script = "files/deploy.sh"
}Создайте файл deploy.sh в директории terraform/files с содержимым
#!/bin/bash
set -e
APP_DIR=${1:-$HOME}
git clone -b monolith https://github.com/express42/reddit.git $APP_DIR/reddit
cd $APP_DIR/reddit
bundle install
sudo mv /tmp/puma.service /etc/systemd/system/puma.service
sudo systemctl start puma
sudo systemctl enable puma
}
Определим параметры подключения провиженеров к VM. Внутрь ресурса VM, перед определением провижинеров, добавьте следующую секцию:
connection {
type = "ssh"
user = "appuser"
agent = false
private_key = "${file("~/.ssh/appuser")}"
}
В данном примере мы указываем, что провижинеры, определенные в ресурсе VM, должны подключаться к созданной VM по SSH, используя для подключения приватный ключ пользователя appuser
- Ресурс для VM сейчас выглядит следующим образом
resource "google_compute_instance" "app" {
name = "reddit-app"
machine_type = "g1-small"
zone = "europe-west1-b"
# определение загрузочного диска
boot_disk {
initialize_params {
image = "reddit-base"
}
}
metadata {
ssh-keys = "appuser:${file("~/.ssh/appuser.pub")}"
}
tags = ["reddit-app"]
# определение сетевого интерфейса
network_interface {
# сеть, к которой присоединить данный интерфейс
network = "default"
# использовать ephemeral IP для доступа из Интернет
access_config {}
}
connection {
type = "ssh"
user = "appuser"
agent = false
private_key = "${file("~/.ssh/appuser")}"
}
provisioner "file" {
source = "files/puma.service"
destination = "/tmp/puma.service"
}
provisioner "remote-exec" {
script = "files/deploy.sh"
}
}
Terraform предлагает команду taint, которая
позволяет пометить ресурс, который terraform
должен пересоздать, при следующем запуске terraform apply.
Говорим terraform'y пересоздать ресурс VM при
следующем применении изменений:
$ terraform taint google_compute_instance.app
The resource google_compute_instance.app in the module root
has been marked as tainted!
Планируем изменения:
$ terraform plan
...
-/+ google_compute_instance.app (tainted) (new resource required)
boot_disk.#: "1" => "1"
boot_disk.0.auto_delete: "true" => “true"
-/+ означает, что ресурс будет удален и создан вновь
$ terraform apply
Определим соответствующие параметры ресурсов main.tf через переменные:
provider "google" {
version = "1.4.0"
project = "${var.project}"
region = "${var.region}"
}
boot_disk {
initialize_params {
image = "${var.disk_image}"
}
}
metadata {
ssh-keys = "appuser:${file(var.public_key_path)}"
}
В директории terraform создайте файл terraform.tfvars, в котором определим переменные.
project = "infra-179015"
public_key_path = "~/.ssh/appuser.pub"
disk_image = "reddit-base"
Пересоздадим все ресурсы созданные при помощи terraform.
$ terraform destroy
...
Do you really want to destroy?
Terraform will delete all your managed infrastructure, as shown above.
There is no undo. Only 'yes' will be accepted to confirm.
Enter a value: yes
Затем создадим ресурсы вновь. Terraform автоматически будет использовать переменные, определенные в terraform.tfvars
$ terraform plan
$ terraform apply
- Определите input переменную для приватного ключа, использующегося в определении подключения для провижинеров (connection);
connection {
type = "ssh"
user = "appuser"
agent = false
private_key = "${file(var.private_key)}"
}
- Определите input переменную для задания зоны в ресурсе "google_compute_instance" "app". У нее должно быть значение по умолчанию;
main.tf
zone = "${var.zone}
variable.tf
variable zone {
description = "Zone"
}
terraform.tfvars
zone = "zone name"
Определим ресурс файервола.
resource "google_compute_firewall" "firewall_ssh" { name = "default-allow-ssh"
network = "default"
allow {
protocol = "tcp" ports = ["22"]
}
source_ranges = ["0.0.0.0/0"] }
Выполним команду применения изменений: ...
google_compute_firewall.firewall_ssh: 1 error(s) occurred:
$ terraform apply
google_compute_instance.app: Refreshing state... (ID: reddit-app)
*google_compute_firewall.firewall_ssh: Error creating firewall: googleapi: Error 409: The resource 'projects/infra-179014/global/firewalls/default-allow-ssh' already exists, alreadyExists
Правило firewall/default-allow-ssh уже существует.
Импортируем существующую инфраструктуру в Terraform
$ terraform import google_compute_firewall.firewall_ssh default-allow-ssh
google_compute_firewall.firewall_ssh: Importing from ID "default-allow-ssh"...
google_compute_firewall.firewall_ssh: Import complete!
Imported google_compute_firewall (ID: default-allow-ssh)
google_compute_firewall.firewall_ssh: Refreshing state... (ID: default-allow-ssh)
Import successful!
Зададим IP для инстанса с приложением в виде внешнего ресурса. Для этого определим ресурс google_compute_address в конфигурационном файле main.tf
resource "google_compute_address" "app_ip" { name = "reddit-app-ip"
}
Ссылаемся на атрибуты другого ресурса Для того чтобы использовать созданный IP адрес в нашем ресурсе VM нам необходимо сослаться на атрибуты ресурса, который этот IP создает, внутри конфигурации ресурса VM. В конфигурации ресурса VM определите, IP адрес для создаваемого инстанса.
network_interface { network = "default" access_config = {
nat_ip = "${google_compute_address.app_ip.address}" }
15
}
Ссылку в одном ресурсе на атрибуты другого тераформ понимает как зависимость одного ресурса от другого. Это влияет на очередность создания и удаления ресурсов при применении изменений. Вновь пересоздадим все ресурсы и посмотрим на очередность создания ресурсов сейчас
$ terraform destroy
$ terraform plan
$ terraform apply
google_compute_address.app_ip: Creating...
google_compute_firewall.firewall_puma: Creating...
google_compute_firewall.firewall_ssh: Creating...
...
google_compute_address.app_ip: Creation complete after 12s (ID: reddit-app-ip)
google_compute_instance.app: Creating...
Видим, что ресурс VM начал создаваться только после завершения создания IP адреса в результате неявной зависимости этих ресурсов.
Вынесем БД на отдельный инстанс VM. Для этого необходимо в директории packer, где содержатся ваши шаблоны для билда VM, создать два новых шаблона db.json и app.json. При помощи шаблона db.json должен собираться образ VM, содержащий установленную MongoDB. Шаблон app.json должен использоваться для сборки образа VM, с установленными Ruby. В качестве базового образа для создания образа возьмем ubuntu16.04.
Разобьем конфиг main.tf на несколько конфигов. Создадим файл app.tf, куда вынесем конфигурацию для VM с приложением.
Образ приложения.
variables.tf:
variable app_disk_image {
description = "Disk image for reddit app" default = "reddit-app-base"
}
app.tf
resource "google_compute_instance" "app" { name = "reddit-app"
machine_type = "g1-small"
zone = "${var.zone}"
tags = ["reddit-app"] boot_disk {
initialize_params {
image = "${var.app_disk_image}"
} }
network_interface { network = "default" access_config = {
nat_ip = "${google_compute_address.app_ip.address}" }
}
metadata {
ssh-keys = "appuser:${file(var.public_key_path)}" }
}
Добавим в app.tf определение правила фаервола для сервера приложения и создание IP адреса.
resource "google_compute_address" "app_ip" { name = "reddit-app-ip"
}
resource "google_compute_firewall" "firewall_puma" {
name = "allow-puma-default" network = "default"
allow {
protocol = "tcp" ports = ["9292"]
}
source_ranges = ["0.0.0.0/0"] target_tags = ["reddit-app"]
}
Объявить переменную в variables.tf
variable db_disk_image {
description = "Disk image for reddit db" default = "reddit-db-base"
}
Создадим файл db.tf, в котором определим ресурсы для запуска VM с БД.
resource "google_compute_instance" "db" { name = "reddit-db"
machine_type = "g1-small"
zone = "${var.zone}"
tags = ["reddit-db"] boot_disk {
initialize_params {
image = "${var.db_disk_image}"
} }
network_interface { network = "default" access_config = {}
}
metadata {
ssh-keys = "appuser:${file(var.public_key_path)}" }
}
Добавим в db.tf правило файервола, которое даст доступ приложению к БД
resource "google_compute_firewall" "firewall_mongo" { name = "allow-mongo-default"
network = "default"
allow {
protocol = "tcp" ports = ["27017"]
}
правило применимо к инстансам с тегом ...
target_tags = ["reddit-db"]
порт будет доступен только для инстансов с тегом ... source_tags = ["reddit-app"] }
Создадим файл vpc.tf в который вынесем правило фаервола для ssh доступа, которое применимо для всех инстансов нашей сети.
resource "google_compute_firewall" "firewall_ssh" { name = "default-allow-ssh"
network = "default"
allow {
protocol = "tcp" ports = ["22"]
}
source_ranges = ["0.0.0.0/0"] }
В файле main.tf остаться только определение провайдера:
provider "google" {
version = "1.4.0"
project = "${var.project}" region = "${var.region}"
}
Разбиваем нашу конфигурацию на отдельные конфиг файлы.
Внутри директории terraform создайте директорию modules, в которой мы будет определять модули.
DB module Внутри директории modules создайте директорию db, в которой создайте три привычных нам файла main.tf, variables.tf, outputs.tf. Скопируем содержимое db.tf, который мы создали ранее, в modules/db/main.tf. Затем определим переменные, которые у нас используются в db.tf и объявляются в variables.tf в файл переменных модуля modules/db/variables.tf
variable public_key_path {
description = "Path to the public key used to connect to instance"
}
variable zone { description = "Zone"
}
variable db_disk_image {
description = "Disk image for reddit db" default = "reddit-db-base"
App module
Создадим по аналогии модуль приложения: в директории modules создадим директорию app, в которой создайте три привычных нам файла main.tf, variables.tf, outputs.tf.
Скопируем содержимое app.tf, который мы создали ранее, в modules/app/main.tf
Затем определим переменные, которые у нас используются в app.tf и объявляются в variables.tf в файл переменных модуля modules/app/variables.tf
variable public_key_path {
description = "Path to the public key used to connect to instance"
}
variable zone { description = "Zone"
}
variable app_disk_image {
description = "Disk image for reddit app" default = "reddit-app-base"
}
Выходные переменные
modules/app/outputs.tf
output "app_external_ip" {
value = "${google_compute_instance.app.network_interface.0.access_config.
0.assigned_nat_ip}"
}
Проверим работу модулей
В файл main.tf, где у нас определен провайдер вставим секции вызова созданных нами модулей
terraform/main.tf
...
module "app" {
Источник, откуда копировать модуль
source = "modules/app" public_key_path = "${var.public_key_path}" zone = "${var.zone}" app_disk_image = "${var.app_disk_image}"
}
module "db" {
source = "modules/db" public_key_path = "${var.public_key_path}" zone = "${var.zone}"
}
db_disk_image
Используем команду для загрузки модулей. В директории terraform:
$ terraform get
Модули будут загружены в директорию .terraform, в которой уже содержится провайдер
Получаем output переменные из модуля
В созданном нами модуле app мы определили выходную переменную для внешнего IP инстанса. Чтобы получить значение этой переменной, переопределим ее
output "app_external_ip" {
value = "${module.app.app_external_ip}"
}
Самостоятельное задание
Аналогично предыдущим модулям создайте модуль vpc, в котором определите настройки файервола в рамках сети. Используйте созданный модуль в основной конфигурации terraform/main.tf
resource "google_compute_firewall" "firewall_ssh" {
name = "default-allow-ssh"
network = "default"
allow {
protocol = "tcp"
ports = ["22"]
}
source_ranges = "${var.source_ranges}"
}
Теперь мы можем задавать диапазоны IP адресов для правила файервола при вызове модуля. terraform/main.tf
...
module "vpc" {
source = "modules/vpc"
source_ranges = ["внешний ip"] }
Самостоятельное задание Проверьте работу параметризованного в прошлом слайде модуля vpc.
Введите в source_ranges не ваш IP адрес, примените правило и проверьте отсутствие соединения к обоим хостам по ssh. Проконтролируйте, как изменилось правило файрвола в веб консоли. Нет доступа к хостам app и db в фильтрах измениля ip адрес
Введите в source_ranges ваш IP адрес, примените правило и проверьте наличие соединения к обоим хостам по ssh. Верните 0.0.0.0/0 в source_ranges. Переиспользование модулей
В директории terrafrom создайте две директории: stage и prod. Скопируйте файлы main.tf, variables.tf, outputs.tf, terraform.tfvars из директории terraform в каждую из созданных директорий. Поменяйте пути к модулям в main.tf на "../modules/xxx" вместо "modules/xxx".
Инфраструктура в обоих окружениях будет идентична, однако будет иметь небольшие различия: мы откроем SSH доступ для всех IP адресов в окружении Stage, а в окружении Prod откроем доступ только для своего IP.
terraform/stage/main.tf
provider "google" {
version = "1.4.0"
project = "${var.project}"
region = "${var.region}"
}
module "app" {
source = "../modules/app"
public_key_path = "${var.public_key_path}"
app_disk_image = "${var.app_disk_image}"
}
module "db" {
source = "../modules/db"
public_key_path = "${var.public_key_path}"
db_disk_image = "${var.db_disk_image}"
}
module "vpc" {
source = "../modules/vpc"
source_ranges = ["0.0.0.0/0"]
}
terraform/prod/main.tf
provider "google" {
version = "1.4.0"
project = "${var.project}"
region = "${var.region}"
}
module "app" {
source = "../modules/app"
public_key_path = "${var.public_key_path}"
app_disk_image = "${var.app_disk_image}"
}
module "db" {
source = "../modules/db"
public_key_path = "${var.public_key_path}"
db_disk_image = "${var.db_disk_image}"
}
module "vpc" {
source = "../modules/vpc"
source_ranges = ["82.155.222.156/32"]
}
Давайте попробуем воспользоваться модулем storage-bucket для создания бакета в сервисе Storage.
Создайте в папке terraform файл
storage-bucket.tf с таким содержанием:
provider "google" { version = "1.4.0"
project = "${var.project}" region = "${var.region}"
}
module "storage-bucket" {
source = "SweetOps/storage-bucket/google"
version = "0.1.1"
name = ["storage-bucket-test", "storage-bucket-test2"]
}
output storage-bucket_url {
value = "${module.storage-bucket.url}"
}
Работа с реестром модулей
Проверьте с помощью gsutil или веб консоли, что бакеты создались и доступны.
gsutil ls
gs://prod-storage-bucket/
gs://stage-storage-bucket/
Управление конфигурацией. Основные DevOps инструменты. Знакомство с Ansible
Установка Ansible.
pip install -r requirements.txt
Проверяем, что Ansible установлен:
$ ansible --version
ansible 2.4.x.x
Поднимим инфраструктуру, описанную в окружении stage
$ terraform apply
app_external_ip = x.x.x.x
db_external_ip = x.x.x.x
Создаем инвентори файл ansible/inventory
appserver ansible_host=35.195.186.154 ansible_user=appuser \
ansible_private_key_file=~/.ssh/appuser
Проверяем что ansible управляет инстансом app
ansible appserver -i ./inventory -m ping
appserver | SUCCESS => {
"changed": false,
"ping": "pong"
}
и инстансом db
$ ansible dbserver -i inventory -m ping
dbserver | SUCCESS => {
"changed": false,
"ping": "pong"
}
Создадим конфигурационный ansible.cfg
[defaults]
inventory = ./inventory
remote_user = appuser
private_key_file = ~/.ssh/appuser
host_key_checking = False
retry_files_enabled = False
Уберем из файла inventory информацию о ssh содинении
appserver ansible_host=35.195.74.54
dbserver ansible_host=35.195.162.174
Проверим работу
$ ansible dbserver -m command -a uptime
dbserver | SUCCESS | rc=0 >>
07:47:41 up 24 min, 1 user, load average: 0.00, 0.00, 0.03
Работа с групой хостов меняем файл inventory
[app]
appserver ansible_host=35.195.74.54
[db]
dbserver ansible_host=35.195.162.174
Проверяем работу
$ ansible app -m ping
appserver | SUCCESS => {
"changed": false,
"ping": "pong"
}
Использование YAML inventory создаем файл inventory.yml
cp inventory inventory.yml
Проверяем что работает с ключем -i которой определяет путь к inventory файлу
$ ansible all -m ping -i inventory.yml
dbserver | SUCCESS => {
"changed": false,
"ping": "pong"
}
appserver | SUCCESS => {
"changed": false,
"ping": "pong"
}
Выполнение команд Проверим, что на app сервере установлены компоненты для работы приложения (ruby и bundler):
$ ansible app -m command -a 'ruby -v'
appserver | SUCCESS | rc=0 >>
ruby 2.3.1p112 (2016-04-26) [x86_64-linux-gnu]
$ ansible app -m command -a 'bundler -v'
appserver | SUCCESS | rc=0 >>
Bundler version 1.11.2
Выполнение команд
Проверим, что на app сервере установлены компоненты для работы приложения (ruby и bundler):
Попробуем указать две команды модулю command:
ansible app -m command -a 'ruby -v; bundler -v' -i inventory.yml
appserver | FAILED | rc=1 >>
ruby: invalid option -; (-h will show valid options) (RuntimeError)non-zero return code
ansible app -m shell -a 'ruby -v; bundler -v' -i inventory.yml
appserver | CHANGED | rc=0 >>
ruby 2.3.1p112 (2016-04-26) [x86_64-linux-gnu]
Bundler version 1.11.2
Модуль shell успешно отработает:
Проверим на хосте с БД статус сервиса MongoDB с помощью модуля command или shell
ansible db -m command -a 'systemctl status mongod' -i inventory.yml
dbserver | CHANGED | rc=0 >>
● mongod.service - High-performance, schema-free document-oriented database
Loaded: loaded (/lib/systemd/system/mongod.service; enabled; vendor preset: enabled)
Active: active (running) since Mon 2018-11-05 18:33:32 UTC; 18min ago
Docs: https://docs.mongodb.org/manual
Main PID: 1273 (mongod)
Tasks: 19
Memory: 52.3M
CPU: 6.673s
CGroup: /system.slice/mongod.service
└─1273 /usr/bin/mongod --quiet --config /etc/mongod.conf
Nov 05 18:33:32 reddit-db systemd[1]: Started High-performance, schema-free document-oriented database.
Пробуем с systemd
ansible db -m systemd -a name=mongod -i inventory.yml
dbserver | SUCCESS => {
"changed": false,
"name": "mongod",
"status": {
"ActiveEnterTimestamp": "Mon 2018-11-05 18:33:32 UTC",
"ActiveEnterTimestampMonotonic": "16141469",
"ActiveExitTimestampMonot,
"ActiveState": "active",
...
C помощью модуля service, который более универсален и будет работать и в более старых ОС с init.dинициализацией:
dnvlasov@resero:~/dnvlasov_infra/ansible$ ansible db -m service -a name=mongod -i inventory.yml
dbserver | SUCCESS => {
"changed": false,
"name": "mongod",
"status": {
"ActiveEnterTimestamp": "Mon 2018-11-05 18:33:32 UTC",
"ActiveEnterTimestampMonotonic": "16141469",
"ActiveExitTimestampMonotonic": "0",
"ActiveState": "active",
Напишем простой плейбук
- name: Clone
hosts: app
tasks:
- name: Clone repo
git: repo: https://github.com/express42/reddit.git
dest: /home/appuser/reddit
И выполним: ansible-playbook clone.yml
PLAY RECAP ***********************************************************************
appserver : ok=2 changed=0 unreachable=0 failed=0
ansible app -m command -a 'rm -rf ~/reddit' -i inventory.yml
[WARNING]: Consider using the file module with state=absent rather than running rm. If you need to use command because file is
insufficient you can add warn=False to this command task or set command_warnings=False in ansible.cfg to get rid of this message.
appserver | CHANGED | rc=0 >>
Деплой и управление конфигурацией с Ansible.
- Создание плейбука Создадим плейбук для управления конфигурацией и деплоя нашего приложения. Для этого создайте файл reddit_app.yml в директории ansible. добавим в файл .gitignore следующую строку: *.retry
- Сценарий для монго
ansible/reddit_app.yml
---
- name: Configure hosts & deploy application
hosts: all
tasks:
- name: Change mongo config file
become: true
template:
src: templates/mongod.conf.j2
dest: /etc/mongod.conf
mode: 0644
tags: db-tag
Для каждого из наших тасков будем определять тег, чтобы иметь возможность запускать отдельные таски, имеющие определенный тег, а не запускать таски все сразу.
- Шаблон конфига MongoDB В директории ansibe/templates создадим файл mongod.conf.j2 Вставим в данный шаблон параметризованный конфиг для MongoDB
# Where and how to store data.
storage:
dbPath: /var/lib/mongodb
journal:
enabled: true
# where to write logging data.
systemLog:
destination: file
logAppend: true
path: /var/log/mongodb/mongod.log
# network interfaces
net:
port: {{ mongo_port | default('27017') }}
bindIp: {{ mongo_bind_ip }}- Пробный прогон
ansible-playbook reddit_app.yml --check --limit dbВидим ошибку: 'mongo_bind_ip' is undefined" переменная,
которая используется в шаблоне не определена
- Определение переменных Определим значения переменных в нашем плейбуке
---
- name: Configure hosts & deploy application
hosts: all
vars:
mongo_bind_ip: 0.0.0.0
tasks:
- name: Change mongo config file
become: true
template:
src: templates/mongod.conf.j2
dest: /etc/mongod.conf
mode: 0644
tags: db-tag- Повторим проверку плейбука
ansible-playbook reddit_app.yml --check --limit dbПроверка прошла успешно
-
Handlers Handlers похожи на таски, однако запускаются только по оповещению других тасков. Таск шлет оповещение handler-у в случае, когда он меняет свое состояние. По этой причине handlers удобно использовать для перезапуска сервисов
-
Добавим handlers
-
ansible/reddit_app.yml
---
- name: Configure hosts & deploy application
hosts: all
vars:
mongo_bind_ip: 0.0.0.0
tasks:
- name: Change mongo config file
become: true
template:
src: templates/mongod.conf.j2
dest: /etc/mongod.conf
mode: 0644
tags: db-tag
notify: restart mongod
handlers:
- name: restart mongod
become: true
service: name=mongod state=restartedПрименим плейбук
- Настройка инстанса приложения Создадим директорию files внутри директории ansible и добавьте туда файл puma.service файл. Добавим в наш сценарий таск для копирования unit файла на хост приложения.
tasks:
- name: Change mongo config file
...
- name: Add unit file for Puma
become: true
copy:
src: files/puma.service
dest: /etc/systemd/system/puma.service
tags: app-tag
notify: reload puma
- name: enable puma
become: true
systemd: name=puma enabled=yes
tags: app-tagНе забудем добавить новый handler, который указывает systemd, что unit для сервиса изменился и его следует перечитать
handlers:
- name: restart mongod
become: true
service: name=mongod state=restarted
- name: reload puma
become: true
systemd: name=puma state=restarted В unit файле добавим строку чтения переменных окружения из файла:
EnvironmentFile=/home/appuser/db_config Создадим шаблон в директории templates/ db_config.j2 куда добавим следующую строку:
DATABASE_URL={{ db_host }}Добавим таск для копирования созданного шаблона:
- name: Add unit file for Puma
...
- name: Add config for DB connection
template:
src: templates/db_config.j2
dest: /home/appuser/db_config
tags: app-tag
- name: enable puma
become: true
systemd: name=puma enabled=yes
tags: app-tagИ не забудем определить переменную db_host
---
- name: Configure hosts & deploy application
hosts: all
vars:
mongo_bind_ip: 0.0.0.0
db_host: 10.132.0.2
tasks:
Пробный прогон:
$ ansible-playbook reddit_app.yml --check --limit
app --tags app-tag Применим наши таски плейбука с тегом app-tag для группы хостов app:
$ ansible-playbook reddit_app.yml --limit app --tags
app-tag Добавим еще несколько тасков в сценарий нашего плейбука Используем модули git и bundle для клонирования последней версии кода нашего приложения и установки зависимых гемов через bundle. ansible/reddit_app.yml
- name: Fetch the latest version of application code
git:
repo:'https://github.com/express42/reddit.git'
dest: /home/appuser/reddit
version: monolith
tags: deploy-tag
notify: reload puma
- name: Bundle install
bundler:
state: present
chdir: /home/appuser/reddit
tags: deploy-tag - Выполняем деплой
$ ansible-playbook reddit_app.yml --check --limit app --tags deploy-tag
$ ansible-playbook reddit_app.yml --limit app --tags deploy-tag Проверяем работу приложения
ip_address:9292
- Сценарий для MongoDB Создадим новый файл reddit_app2.yml в директории ansible. Определим в нем несколько сценариев (plays), в которые объединим задачи, относящиеся к используемым в плейбуке тегам
- Определим отдельный сценарий для управления конфигурацией MongoDB
---
- name: Configure hosts & deploy application
hosts: all
vars:
mongo_bind_ip: 0.0.0.0
tasks:
- name: Change mongo config file
become: true
template:
src: templates/mongod.conf.j2
dest: /etc/mongod.conf
mode: 0644
tags: db-tag
notify: restart mongod
handlers:
- name: restart mongod
become: true
service: name=mongod state=restartedИзменим словесное описание, укажем нужную
группу хостов. Уберем теги из тасков и определим
тег на уровне сценария, чтобы мы могли запускать
сценарий, используя тег.
Вынесем become: true на уровень сценария.
---
- name: Configure MongoDB
hosts: db
tags: db-tag
become: true
vars:
mongo_bind_ip: 0.0.0.0
tasks:
- name: Change mongo config file
template:
src: templates/mongod.conf.j2
dest: /etc/mongod.conf
mode: 0644
notify: restart mongod
handlers:
- name: restart mongod
service: name=mongod state=restartedДля настройки инстанса приложения пометим тегом app-tag. Вставим скопированную информацию в reddit_app2.yml следом за сценарием для MongoDB.
- name: Configure App
hosts: app
tags: app-tag
become: true
vars:
db_host: 10.132.0.2
tasks:
- name: Add unit file for Puma
copy:
src: files/puma.service
dest: /etc/systemd/system/puma.service
notify: reload puma
- name: Add config for DB connection
template:
src: templates/db_config.j2
dest: /home/appuser/db_config
owner: appuser
group: appuser
- name: enable puma
systemd: name=puma enabled=yes
handlers:
- name: reload puma
systemd: name=puma state=restarted- Пересоздадим инфраструктуру
$ terraform destroy
$ terraform apply -auto-approve=false- Проверим работу сценариев db-tag
$ ansible-playbook reddit_app2.yml --tags db-tag --check
$ ansible-playbook reddit_app2.yml --tags db-tag app-tag
$ ansible-playbook reddit_app2.yml --tags app-tag --check
$ ansible-playbook reddit_app2.yml --tags app-tag - name: Deploy App
hosts: app
tags: deploy-tag
tasks:
- name: Fetch the latest version of application code
git:
repo: 'https://github.com/express42/reddit.git'
dest: /home/appuser/reddit
version: monolith
notify: restart puma
- name: bundle install
bundler:
state: present
chdir: /home/appuser/reddit
handlers:
- name: restart puma
become: true
systemd: name=puma state=restartedПроверка сценария
$ ansible-playbook reddit_app2.yml --tags deploy-tag --check
$ ansible-playbook reddit_app2.yml --tags deploy-tag проверка http://ip_address:9292
В директории ansible создадим три новых файла app.yml, db.yml, deploy.yml. mkdir {app,db,deploy}.yml Заодно переименуем наши предыдущие плейбуки:
- reddit_app.yml -> reddit_app_one_play.yml
- reddit_app2.yml-> reddit_app_multiple_plays.yml
Из файла reddit_app_multiple_plays.yml скопируем сценарий, относящийся к настройке БД, в файл db.yml, удалим тег определенный в сценарии.
---
- name: Configure MongoDB
hosts: db
become: true
vars:
mongo_bind_ip: 0.0.0.0
tasks:
- name: Change mongo config file
template:
src: templates/mongod.conf.j2
dest: /etc/mongod.conf
mode: 0644
notify: restart mongod
handlers:
- name: restart mongod
service: name=mongod state=restarted---
- name: Configure App
hosts: app
tags: app-tag <-- удалить tag
become: true
vars:
db_host: 10.132.0.2
tasks:
- name: Add unit file for Puma
copy:
src: files/puma.service
dest: /etc/systemd/system/puma.service
notify: reload puma
- name: Add config for DB connection
template:
src: templates/db_config.j2
dest: /home/appuser/db_config
owner: appuser
group: appuser
- name: enable puma
systemd: name=puma enabled=yes
handlers:
- name: reload puma
systemd: name=puma state=restarted- name: Deploy App
hosts: app
tasks:
- name: Fetch the latest version of application code
git:
repo: 'https://github.com/express42/reddit.git'
dest: /home/appuser/reddit
version: monolith
notify: restart puma
- name: bundle install
bundler:
state: present
chdir: /home/appuser/reddit
handlers:
- name: restart puma
become: true
systemd: name=puma state=restartedСоздадим файл site.yml в директории ansible, в котором опишем управление конфигурацией всей нашей инфраструктуры. Это будет нашим главным плейбуком, который будет включать в себя все остальные
---
- import_playbook: db.yml
- import_playbook: app.yml
- import_playbook: deploy.yml Исрользуем окружение stage
$ terraform destroy
$ terraform apply -auto-approve=false$ ansible-playbook site.yml --check
$ ansible-playbook site.ymlhttp://ip_address:9292
Создадим плейбуки ansible/packer_app.yml
---
- name: Install Ruby && Bundler
hosts: all
become: true
tasks:
- name: Install ruby and rubygems and required packages
apt: "name={{ item }} state=present"
with_items:
- ruby-full
- ruby-bundler
- build-essential
ansible/packer_db.yml.
---
- name: Install MongoDB 3.2
hosts: all
become: true
tasks:
- name: Add APT key
apt_key:
id: EA312927
keyserver: keyserver.ubuntu.com
- name: Add APT repository
apt_repository:
repo: deb [ arch=amd64,arm64 ] http://repo.mongodb.org/apt/ubuntu xenial/mongodb-org/3.2 multiverse
state: present
- name: Install mongodb package
apt:
name: mongodb-org
state: present
- name: Configure service supervisor
systemd:
name: mongod
enabled: yes
Заменим секцию Provision в образе packer/app.json на Ansible
{
"provisioners": [
{
"type": "ansible",
"playbook_file":"ansible/packer_app.yml"
}
]
}Такие же изменения выполним и для packer/db.json
{
"provisioners": [
{
"type": "ansible",
"playbook_file":"ansible/packer_db.yml"
}
]
}Соберем образы db и app
packer build -var-file=packer/variables.json packer/db.json
packer build -var-file=packer/variables.json packer/app.json- Создадим роли
ansible-galaxy init roles/app
ansible-galaxy init roles/dbСтруктура диектории созданной роли db
$tree db
db
├── defaults # <-- Диектория для переменных по умолчанию
│ └── main.yml
├── files
├── handlers
│ └── main.yml
├── meta # <-- Информация о роли, создателе и зависимостях
│ └── main.yml
├── README.md
├── tasks # <-- Директоря для тасков
│ └── main.yml
├── templates
│ └── mongod.conf.j2
├── tests
│ ├── inventory
│ └── test.yml
└── vars # <-- Директория для переменных, которые не должны
└── main.yml # переопределятся пользователем
6 directories, 8 files
- Скопируем секцию tasks в сценарии плейбука ansible/db.yml и вставим в ее в файл в директории tasks роли db
Файл ansible/roles/db/tasks/main.yml
---
# tasks file for db
- name: Show info about the env this host belongs to
debug: msg="This host is in {{ env }} environment!!!"
- name: Change mongo config file
template:
src: mongod.conf.j2
dest: /etc/mongod.conf
mode: 0644
notify: restart mongod
Определяем хендлер в директории handlers роли
# handlers file for db
- name: restart mongod
service: name=mongod state=restartedОпределим используеме в шаблоне переменные в секции переменных по умолчанию (файл ansible/roles/db/defaults/main.yml)
# defaults file for db
mongo_port: 27017
mongo_bind_ip: 127.0.0.1- Выполним команду ansible-galaxy init в папке roles/app
- Скопируем в секцию tasks сценарий плейбука ansible/app.yml
ansible/roles/app/tasks/main.yml
# tasks file for app
- name: Add unit file for Puma
copy:
src: puma.service
dest: /etc/systemd/system/puma.service
notify: reload puma
- name: Add config for DB connection
template:
src: db_config.j2
dest: /home/appuser/db_config
owner: appuser
group: appuser
- name: enable puma
systemd: name=puma enabled=yes- Скопируем db_config.j2 из директории ansible/templates в директорию ansible/roles/app/templates
- Файлы ansible/files/puma.service в ansible/roles/app/files
- Опишим хендлер соответствующий директории app
ansible/role/app/handlers/main.yml
---
# handlers file for app
- name: reload puma
systemd: name=puma state=restartedТакже определим переменную по умолчанию подключения к MongoDB
ansible/role/app/defaults/main.yml
---
# defaults file for app
db_host: 127.0.0.1
env: localУдалим определение тасков и хендлеров в плейбуке ansible/app.yml
---
- name: Configure App
hosts: app
become: true
vars:
db_host: 10.140.0.2
roles:
- app
Пересоздадим окружение stage terraform
terraform destroy
terraform apply --auto-approve=falseЗапустим плейбуки
./d_invent.sh site.yml --check
./d_invent.sh site.yml Проверим работу приложения
http://ip_address:9292
- Определим окружение по умолчанию
[defaults]
inventory = ./environments/stage/inventory
remote_user = appuser
private_key_file = ~/.ssh/appuser
host_key_checking = FalseAnsible позволяет задавать переменные для группы хостов. директория group_vars позволяет создавать файлы (имена, которых должны соответствовать названиям групп в инвентори файле) для определения переменных для группы хостов.
- Создадим директорию group_vars в директориях наших
окружений
nvironments/prodиenvironments/stage.
- Зададим настройки окружения stage, используя групповые переменные:
- Создадим файлы
stage/group_vars/appдля определения переменных для группы хостов app, описанных в инвентори файлеstage/inventory - Скопируем в этот файл переменные, определенные в плейбуке
ansible/app.yml. - Также удалим определение переменных из самого плейбука
ansible/app.yml.
- Аналогичным образом определим переменные для группы хостов БД на окружении stage:
- Создадим файл
stage/group_vars/dbи скопируем в него содержимое переменные из плейбукаansible/db.yml - Секцию определения переменных из самого плейбука
ansible/db.ymlудалим.
- Создайте файл
ansible/environments/stage/group_vars/allсо следующим содержимым:
env: stage- Для настройки окружения prod скопируйте файлы app, db, all из директории stage/group_vars в директорию prod/group_vars.
- В файле prod/group_vars/all измените значение env переменной на prod
env: prodДля роли app в файле ansible/roles/app/defaults/main.yml:
---
# defaults file for app
db_host: 127.0.0.1
env: localДля роли db в файле ansible/roles/db/defaults/main.yml:
---
# defaults file for db
mongo_port: 27017
mongo_bind_ip: 127.0.0.1
env: localБудем выводить информацию о том, в каком окружении находится конфигурируемый хост. Воспользуемся модулем debug для вывода значения переменной. Добавим следующий таск в начало наших ролей.
- Для роли app (файл
ansible/roles/app/tasks/main.yml):
---
# tasks file for app
- name: Show info about the env this host belongs to
debug:
msg: "This host is in {{ env }} environment!!!"Добавим такой же таск в роль db (файл ansible/roles/db/tasks/main.yml)
---
# tasks file for db
- name: Show info about the env this host belongs to
debug: msg="This host is in {{ env }} environment!!!"[defaults]
inventory = ./environments/stage/inventory
remote_user = appuser
private_key_file = ~/.ssh/appuser
host_key_checking = False
retry_files_enabled = False
roles_path = ./roles
vault_password_file = ~/.ansible/vault.key
[diff]
always = True
context = 5Для проверки пересоздадим инфраструктуру окружения stage, используя команды:
$ terraform destroy
$ terraform apply -auto-approve=falseТеперь запустим Ansible...
$ ./d_invent.sh playbooks/site.yml --check
$ ./d_invent.sh playbooks/site.ymlПроверим работу приложения
ip_address:9292
Для проверки настройки prod окружения сначала удалим инфраструктуру окружения stage. Затем поднимем инфраструктуру для prod окружения.
- Перед проверкой не забудьте изменить внешние IP-адреса
инстансов в инвентори файле
ansible/environments/prod/inventoryи переменнуюdb_hostвprod/group_vars/app
Если все сделано правильно, то получим примерно такой вывод команды ansible-playbook:
$ ansible-playbook -i environments/prod/inventory playbooks/site.yml --check
$ ansible-playbook -i environments/prod/inventory playbooks/site.ymlПроверим работу приложения ip_address:9292
Хорошей практикой является разделение зависимостей ролей (requirements.yml) по окружениям
- Создадим файлы environments/stage/requirements.yml и environments/prod/requirements.yml
- Добавим в них запись вида:
- src: jdauphant.nginx
version: v2.21.1- Установим роль
ansible-galaxy install -r environments/stage/requirements.yml- Комьюнити-роли не стоит коммитить в свой репозиторий, для этого добавим в .gitignore запись: jdauphant.nginx
- для минимальной настройки проксирования необходимо добавить следующие переменные:
db_host: 10.140.0.2
nginx_sites:
default:
- listen 80
- server_name "reddit"
- location / {
proxy_pass http://127.0.0.1:9292;
}Добавим эти переменные в stage/group_vars/app и prod/group_vars/app
- Самостоятельное задание
- Добавить в конфигурацию Terraform открытие 80 порта для инстанса приложения.
resource "google_compute_firewall" "firewall_puma" {
name = "allow-puma-default"
network = "default"
allow {
protocol = "tcp"
ports = ["9292","80"]
}
source_ranges = ["0.0.0.0/0"]
target_tags = ["reddit-app"]
}- Добавьте вызов роли jdauphant.nginx в плейбук app.yml
---
- name: Configure App
hosts: app
become: true
vars:
db_host: ip_address
roles:
- app
- jdauphant.nginx- Применим плейбук site.yml для окружения stage и проверям, что приложение доступно на 80 порту
ip_adderss
Подготовим плейбук для создания пользователей, пароль пользователей будем хранить в зашифрованном виде в файле credentials.yml:
- Создаим файл vault.key со произвольной строкой ключа в папке ~/.ansible/vault.key
- Изменим файл ansible.cfg, добавим опцию vault_password_file в секцию [defaults]
[defaults]
vault_password_file = ~/.ansible/vault.keyДобавим плейбук для создания пользователей - файл ansible/playbooks/users.yml
- name: Create users
hosts: all
become: true
vars_files:
- "{{ inventory_dir }}/credentials.yml"
tasks:
- name: create users
user:
name: "{{ item.key }}"
password: "{{ item.value.password|password_hash('sha512',
65534|random(seed=inventory_hostname)|string) }}"
groups: "{{ item.value.groups | default(omit) }}"
with_dict: "{{ credentials.users }}"Создадим файл с данными пользователей для каждого окружения
- Файл для prod (ansible/environments/prod/credentials.yml):
credentials:
users:
admin:
password: admin123
groups: sudo- Файл для stage (ansible/environments/stage/credentials.yml)
credentials:
users:
admin:
password: qwerty123
groups: sudo
qauser:
password: test123- Зашифруем файлы используя vault.key (используем одинаковый для всех окружений):
$ ansible-vault encrypt environments/prod/credentials.yml
$ ansible-vault encrypt environments/stage/credentials.yml- Проверим содержимое файлов, убедитесь что они зашифрованы
$ANSIBLE_VAULT;1.1;AES256
65373062643736386435363238626365363032303435653965353534646437653035383461346666
6663303562323032616238356134346438343039323266360a646337653531343039303432666133
39393530383264663839396435363763303965346464643363383132663761363564343265346463
6531363733643131310a363931323030393931643632633162383238316137393937313166343630
34313732613164636635616334373332623339313234393633633133663631306466623261323333
33343766663461613238663833393962323937613139626334353764373062336438663139623230
34386632353436653632383262333339396537633936633164323133626562633561633632663434
39633930356664316332646461663736376234656630393136623933633437626132656234623738
38303933663333643933363630313539393064646631663937323538346262613134316665313337
32333837353230346331316439646538373632353737353339636664646165633731663838656661
30323266623634363966656133626539373962646366356330356532373861636236623064616366
30343139396163303562353161623336396630323139393932353039303336316561316533363137
34636538326133316238366364356262613236303538336138643462343337323434383935636538
66353430666461663833666535323936613232303234626637303138626632356136623730383466
333135383063643161303434616637326430- Добавьте вызов плейбука в файл site.yml и выполните его для stage окружения:
---
- import_playbook: db.yml
- import_playbook: app.yml
- import_playbook: deploy.yml
- import_playbook: users.yml
- Проверим что пользователи созданы в системе.
- Для проверки доступа по паролю изменим конфиг ssh
vi /etc/ssh/sshd_config
PasswordAuthentication yes
/etc/init.d/sshd restartВ source.list добавим repo
echo "deb https://download.virtualbox.org/virtualbox/debian <mydist> contrib" >> /etc/apt/source.listДобавим ключи repo
wget -q https://www.virtualbox.org/download/oracle_vbox_2016.asc -O- | sudo apt-key add -
wget -q https://www.virtualbox.org/download/oracle_vbox.asc -O- | sudo apt-key add -Обновим repo пакетов и установим vagrant
sudo apt-get update
sudo apt-get install virtualbox-6.0Устанавливаем vagrant
apt install vagrantПеред началом работы добавим следующие строки в .gitignore
# Vagrant & molecule
.vagrant/
*.log
*.pyc
.molecule
.cache
.pytest_cacheВ директории ansible создаем файл Vagrantfile с определением двух VM:
Vagrant.configure("2") do |config|
config.vm.provider :virtualbox do |v|
v.memory = 512 <---------- Количество памяти выделяемое под VMs
end
config.vm.define "dbserver" do |db|
db.vm.box = "ubuntu/xenial64"
db.vm.hostname = "dbserver" <--------- Имя VM
db.vm.network :private_network, ip: "10.10.10.10"
config.vm.define "appserver" do |app|
app.vm.box = "ubuntu/xenial64" <---------- Название бокса образа VM
app.vm.hostname = "appserver"
app.vm.network :private_network, ip: "10.10.10.20" <---- ip внутреннего интерфейса
end
end
Создадим виртуалки
$ vagrant upПроверяем что бокс скачался на локальную машину
$ vagrant box list
ubuntu/xenial64 (virtualbox, 20181129.0.0)Проверим статус VMs
$ vagrant status
Current machine states:
dbserver running (virtualbox)
appserver running (virtyalbox)Проверим SSH доступ к VM с название appserver и пинг хоста dbserver
$ vagrant ssh appserver
Welcome to Ubuntu 16.04.3 LTS (GNU/Linux 4.4.0-96-generic x86_64)
ubuntu@appserver:~$ ping -c 2 10.10.10.10
ubuntu@appserver:~$ exit
logout
Connection to 127.0.0.1 closed.Vagrant поддерживает большое количество провижинеров, которые позволяют автоматизировать процесс конфигурации созданных VMs с использованием популярных инструментов управления конфигурацией и обычных скриптов на bash будем использовать Ansible провижинер для проверки работы наших ролей и плейбуков
Начнем с доработки db роли.
Добавим провижининг в определение хоста dbserver:
config.vm.define "dbserver" do |db|
db.vm.box = "ubuntu/xenial64"
db.vm.hostname = "dbserver"
db.vm.network :private_network, ip: "10.10.10.10"
db.vm.provision "ansible" do |ansible|
ansible.playbook = "playbooks/site.yml"
ansible.groups = {
"db" => ["dbserver"],
"db:vars" => {"mongo_bind_ip" => "0.0.0.0"}
}
end
end
Применяем провиженер
$ vagrant provision dbserverДобавим установку python
ansible/playbooks/base.yml
---
- name: Check && install python
hosts: all
become: true
gather_facts: False
tasks:
- name: Install python for Ansible
raw: test -e /usr/bin/python || (apt -y update && apt install -y python-minimal)
changed_when: FalseПовторим попытку провижининга хоста dbserver:
$ vagrant provision dbserver
==> dbserver: Running provisioner: ansible...
TASK [db : Show info about the env this host belongs to] ***********************
ok: [dbserver] => {
"msg": "This host is in local environment!!!"
}
TASK [db : Change mongo config file] *******************************************
changed: [dbserver]
RUNNING HANDLER [db : restart mongod] ******************************************
fatal: [dbserver]: FAILED! => {"changed": false, "failed": true, "msg": "Could
not find the requested service mongod: host"}
to retry, use: --limit @/Users/user/hw133/ansible/site.retryAnsible прогнал таски роли: положил конфиг монги и попытался ее перезапустить, но... не нашел установленной монги.
Изменим роль db, добавив файл тасков db/tasks/install_mongo.yml для установки MongoDB. Добавим к каждому таску тег install, пометив егокак шаг установки.
Скопируем таски из файла packer_db.yml вставим их в файл db/tasks/install_mongo.yml
---
- name: Change mongo config file
template:
src: templates/mongod.conf.j2
dest: /etc/mongod.conf
mode: 0644
notify: restart mongodВ файле main.yml роли будем вызывать таски в нужном нам порядке: db/tasks/main.yml
---
# tasks file for db
- name: Show info about the env this host belongs to
debug: msg="This host is in {{ env }} environment!!!"
- include: install_mongo.yml
- include: config_mongo.ymlПрименим роль для локальной машины dbserver Видим, что провижининг выполнился успешно. Проверим доступность порта монги для хоста appserver, используя команду telnet:
$ vagrant ssh appserver
Welcome to Ubuntu 16.04.3 LTS (GNU/Linux 4.4.0-96-generic x86_64)
Last login: Tue Sep 26 14:13:40 2017 from 10.0.2.2
ubuntu@appserver:~$ telnet 10.10.10.10 27017
Trying 10.10.10.10...
Connected to 10.10.10.10.
Escape character is '^]'. Подключение удалось, значит порт доступен для хоста appserver и конфигурация роли верна.
Аналогично роли db мы включим в нашу роль app конфигурацию из packer_app.yml плейбука, необходимую для настройки хоста приложения Создадим новый файл для тасков ruby.yml внутри роли app и скопируем в него таски из плейбука packer_app.ym
app/tasks/ruby.yml
---
- name: Install ruby and rubygems and required packages
apt: "name={{ item }} state=present"
with_items:
- ruby-full
- ruby-bundler
- build-essential
tags: rubyВынесем настройки puma сервера также в отдельный файл для тасков в рамках роли. Создадим файл app/tasks/puma.yml и скопируем в него таски из app/tasks/ main.yml, относящиеся к настройке Puma сервера и запуску приложения
app/tasks/puma.yml
---
- name: Add unit file for Puma
template:
src: puma.service.j2
dest: /etc/systemd/system/puma.service
notify: reload puma
- name: Add config for DB connection
template:
src: db_config.j2
dest: /home/appuser/db_config
owner: appuser
group: appuser
- name: enable puma
systemd: name=puma enabled=yes В файле main.yml роли будем вызывать таски в нужном нам порядке:
app/tasks/main.yml
---
# tasks file for app
- name: Show info about the env this host belongs to
debug:
msg: "This host is in {{ env }} environment!!!"
- include: ruby.yml
- include: puma.ymlАналогично dbserver определим Ansible провижинер для хоста appserver в Vagrantfile:
config.vm.define "appserver" do |app|
app.vm.box = "ubuntu/xenial64"
app.vm.hostname = "appserver"
app.vm.network :private_network, ip: "10.10.10.20"
app.vm.provision "ansible" do |ansible|
ansible.playbook = "playbooks/site.yml"
ansible.groups = {
"app" => ["appserver"],
"app:vars" => { "db_host" => "10.10.10.10"}
}
end
end
Vagrant inventory file
$ cat .vagrant/provisioners/ansible/inventory/vagrant_ansible_inventory
# Generated by Vagrant
...
dbserver ansible_ssh_host=127.0.0.1 ansible_ssh_port=2201
ansible_ssh_user='ubuntu' ansible_ssh_private_key_file='/Users/user/
hw133/ansible/.vagrant/machines/dbserver/virtualbox/private_key'
[db]
dbserver
[db:vars]
mongo_bind_ip=0.0.0.0Проверка роли
$ vagrant provision appserver Параметризуем имя пользователя, чтобы дать возможность использовать роль для иного пользователя.
app/defaults/main.yml
---
# defaults file for app
db_host: 127.0.0.1
env: local
deploy_user: appuserДалее параметризуем сам unit файл. Переместим его из директории app/files в директорию app/ templates, т.к. мы поменяли используемый для копирования модуль и добавим к файлу puma.service расширение .j2, чтобы обозначить данный файл как шаблон.
Меняем в созданном шаблоне все упоминания appuser на переменную deploy_user
[Unit]
Description=Puma HTTP Server
After=network.target
[Service]
Type=simple
EnvironmentFile=/home/{{ deploy_user }}/db_config
User={{ deploy_user }}
WorkingDirectory=/home/{{ deploy_user }}/reddit
ExecStart=/bin/bash -lc 'puma'
Restart=always
[Install]
WantedBy=multi-user.targetСнова обратимся к app/tasks/puma.yml и параметризуем оставшуюся конфигурацию
---
- name: Add unit file for Puma
template:
src: puma.service.j2
dest: /etc/systemd/system/puma.service
notify: reload puma
- name: Add config for DB connection
template:
src: db_config.j2
dest: "/home/{{ deploy_user }}/db_config"
owner: "{{ deploy_user }}"
group: "{{ deploy_user }}"
- name: enable puma
systemd: name=puma enabled=yes И playbooks deploy.yml которвй применяется для группы хостов app
- name: Deploy App
hosts: app
tasks:
- name: Fetch the latest version of application code
git:
repo: 'https://github.com/express42/reddit.git'
dest: "/home/{{ deploy_user }}/reddit"
version: monolith
notify: restart puma
- name: bundle install
bundler:
state: present
chdir: "/home/{{ deploy_user }}/reddit"
handlers:
- name: restart puma
become: true
systemd: name=puma state=restarted
Используем переменные extra_vars, имеющие самый высокий приоритет по сравнению со всеми остальными.
Добавим extra_vars переменные в блок определения провижинера в Vagrantfile
app.vm.provision "ansible" do |ansible|
ansible.playbook = "site.yml"
ansible.groups = {
"app" => ["appserver"],
"app:vars" => { "db_host" => "10.10.10.10"}
}
ansible.extra_vars = {
"deploy_user" => "ubuntu"
}
end
Проверяем работу приложения http://ip_adderss:9292
Установка зависимостей.
Добавьте в файл requirements.txt в директории ansible следующие записи:
ansible/requirements.txt
ansible>=2.4
molecule>=2.6
testinfra>=1.10
python-vagrant>=0.5.15Используем команду molecule init для создания заготовки тестов для роли db. Выполним команду в директории с ролью ansible/roles/db:
molecule init scenario --scenario-name default -r db -d vagrant Добавим несколько тестов, используя модули Testinfra, для проверки конфигурации, настраиваемой ролью db:
import os
import testinfra.utils.ansible_runner
testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('all')
# check if MongoDB is enabled and running
def test_mongo_running_and_enabled(host):
mongo = host.service("mongod")
assert mongo.is_running
assert mongo.is_enabled
# check if configuration file contains the required line
def test_config_file(host):
config_file = host.file('/etc/mongod.conf')
assert config_file.contains('bindIp: 0.0.0.0')
assert config_file.is_fileОписание тестовой машины, которая создается Molecule для тестов содержится в файле db/molecule/default/molecule.yml
---
dependency:
name: galaxy
driver:
name: vagrant
provider:
name: virtualbox
lint:
name: yamllint
platforms:
- name: instance
box: ubuntu/xenial64
provisioner:
name: ansible
lint:
name: ansible-lintСоздадим VM для проверки роли. В директории ansible/roles/db выполним команду:
$ molecule create Посмотрим список созданных инстансов, которыми управляет Molecule:
$ molecule list
Instance Name Driver Name Provisioner Name Created Converged
--------------- ------------- ------------------ --------- -----------
instance Vagrant Ansible True False
Molecule init генерирует плейбук для применения нашей роли. Данный плейбук можно посмотреть по пути db/molecule/default/playbook.yml
Поскольку таски нашей роли требуют выполнения из-под суперпользователя, то добавим become в определение сценария этого плейбука.
Дополнительно зададим еще переменную mongo_bind_ip
---
- name: Converge
become: true
hosts: all
vars:
mongo_bind_ip: 0.0.0.0
roles:
- role: dbПрименим playbook.yml, в котором вызывается наша роль к созданному хосту:
$ molecule converge Прогоним тесты
$ molecule verify Написать тест к роли db для проверки того, что БД слушает по нужному порту (27017)
import os
import testinfra.utils.ansible_runner
testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
os.environ['MOLECULE_INVENTORY_FILE']).get_hosts('all')
# check if MongoDB is enabled and running
def test_mongo_running_and_enabled(host):
mongo = host.service("mongod")
assert mongo.is_running
assert mongo.is_enabled
# check if configuration file contains the required line
def test_config_file(host):
config_file = host.file('/etc/mongod.conf')
assert config_file.contains('bindIp: 0.0.0.0')
assert config_file.is_file
def test_socket_listen(host):
socket = host.socket("tcp://0.0.0.0:27017")
assert socket.is_listening
Используйте роли db и app в плейбуках packer_db.yml и packer_app.yml
ansible/packer_app.yml
---
- name: Install Ruby && Bundler
hosts: all
become: true
roles:
- app
ansible/packer_db.yml
---
- name: Install MongoDB 3.2
hosts: all
become: true
roles:
- db