По примеру сокурсника Алексея Степаненко, буду выписывать список команд, которые использовались в ДЗ, т.к. реально помогает быстро освежить память.
vagrant up
vagrant provision <host>
vagrant destroy [-f]
# vurtual env
virtualenv pythonenv
source pythonenv/bin/activate
deactivate
# in role directory
molecule init scenario --scenario-name default -r db -d vagrant
molecule create
molecule list
molecule login -h instance
Лабораторная работа доставила много боли. Т.к. мое рабочее окружение находилось внутри VM virtualbox-а, я не мог установить в свою VM virtualbox. Мне пришлось использовать WSL (Windows system for Linux). Рабочее окружения я оставил внутри VM, проверяя синтаксис, а зател делал push. Внутри WSL я делал pull и вызов vagrant-а. Можно было попробовать использовать LXC или GCE, как советовали в чате, но времени, к сожалению, не так много, как хотелось бы.
В настройки vm box-а Vagrantfile
пришлось добавить опцию, отключающую
serial port, иначе у меня валилась ошибка при создании VM (создавал из под
WSL).
v.customize ['modifyvm', :id, '--uartmode1', 'disconnected']
также пришлось установить переменные окружения:
export VAGRANT_WSL_ENABLE_WINDOWS_ACCESS="1"
export VAGRANT_WSL_WINDOWS_ACCESS_USER_HOME_PATH="/mnt/d/vm"
Были проблемы с деплоем puma - пришлось добавить в конфигурацию deploy.yml
следующие строки, чтобы создавались файлы с правами {{deploy_user}}
become: true
become_user: "{{deploy_user}}"
Первоначально неправильно передал список параметров, на что ansible ругнулся, что
ожидает в playbook-е тип dictionary. Посмотрел файл
./vagrant/provisioners/ansible/inventory/vagrant_ansible_inventory
и стало
понятно в чем проблема. Я не совсем понял, почему одну и туже конструкцию в
host_vars
и extra_vars
vagrant преобразовывает поразному. В первом варианте
он кладет ее в inventory-файл в неправильном формает, во втором корректно
преобразует в yaml.
Устанавливаем виртуальное окружение
virtualenv pythonenv
source pythonenv/bin/activate
После выполнения команды molecule create
мне опять выдает ошибку. В этот момент
я готов был забить на данное ДЗ ;) Пришлось выполнить molecule create --debug
,
из нее узнал, что проблема опять в VirtualBox под WSL. ПОдредактировал файл
molecule.yml
, добавил строчки:
raw_config_args:
- "customize ['modifyvm', :id, '--uartmode1', 'disconnected']"
Тесты прошли успешно.
Для выполение сборки образов packer
с измененным плейбуками packer_app
и
packer_db
пришлось добавить в них переменную :
"extra_arguments": ["--tags","ruby"],
"ansible_env_vars": [
"ANSIBLE_ROLES_PATH=./roles:~/projects/andywow_infra/ansible/roles"
]
Роль была перенесена следующими действиями (с сохранением истории изменений):
# Клонируем текущий репозиторий
git clone --no-hardlinks andywow_infra ansible-mongod
# Переносим директорию ansible/roles/db в корень репозитория и удаляем файлы
# и историю, которые не находятся в текущей диреткорит
git filter-branch --subdirectory-filter ansible/roles/db HEAD
# переносим ссылку HEAD наверх перезаписанной истории
git reset --hard
# Перепаковка объектов и удаление тех, которых нет в истории
git gc --aggressive
# Удаление несуществующих объектов
git prune
После этого в новом репозитории удаляем ветку master
и делаем создаем ее заново
из ветки ansible-4
. Ветку ansible-4
удаляем. Делаем push в новый репозиторий
на github.
Далее удаляем каталог db
из основного репозитория. Историю его изменений я
оставил.
После этого меняем requirements.yml
и устанавливаем роль
ansible-galaxy install -r requirements.yml
В новом репозитории удаляем каталог molecule
и переинциализируем его для работы
с gce:
molecule init scenario --scenario-name default -r ansible-mongod -d gce
В GCE на всякий случай сделал отдельный проект для Travis-а.
Делаем github-token для хранения секретов и используем его для travis ci
travis login --github-token XXXXXXXXXXXXXXXXXXXXXXXXX
Successfully logged in as andywow!
travis encrypt-file gcetravisci.json -add
В GCE у сервисного аккаунта должна быть роль IAM Compute Admin
.
При локальном тестировании molecule пришлось удалять ssh ключить
из authorized_hosts
файла. Это отняло больше всего времени на понимание.
Интеграция с чатом настроена: https://devops-team-otus.slack.com/messages/C8C5LKLF7/
Для открытия 80-го порта создана перменная со значением по-умолчанию true
open_default_http = true
Сама переменная была добавлена в предыдущих ДЗ.
Вызов роли также был добавлен. Плейбук site.xml
применен, приложение работает
на 80-м порту успешно.
ansible-galaxy install -r environments/stage/requirements.yml
Здесь столкнулся с проблемой:
Как определять хосты GCE по группам, если они создаются динамически. Решение было найдено просмотром результатов команды
./gce.py --list
Выяснил, что к GCE хостам можно обращаться по их тегам, т.е. имя группы будет
иметь вид, tag_%tag-name%
, например tag_reddit-app
Сама настройка динамического inventory по-умолчанию происходит в
конфигурационном файл ansible.cfg
inventory = ./environments/stage/gce.py
Старый файл inventory
без расширения пришлось переименовать в
inventory.static
, чтобы добавить возможность его исключения.
Т.е. в каталоге ansible/environments/$env_name
должны лежать файлы
gce.py
, gce.ini
и key.json
Настройки gce.ini
:
gce_service_account_email_address = userid@project_id.iam.gserviceaccount.com
gce_service_account_pem_file_path = path_to_key.json
gce_project_id = project_id
instance_states = RUNNING,PROVISIONING
Сам себе нагорожил кучу проблем из-за переменных путей в вызове команд.
Обнаружил баг у tflint
- он падает с ошибкой, если вызывается модуль и в
качестве значения переменной передается строка вида
db_host = "${var1}:{var2}"
Сделал параллельный вызов билда для двух окружений, если дальше будут более крутые задачи, например, параллеьный деплой в два окружения.
Было создано несколько плейбуков:
reddit_app.yml
, в котором любой сценарий может быть применен к любому хосту
Файл puma.service
пришлось также шаблонизировать (
puma.service.j2), чтобы иметь возможность указать
произвольный порт, на котором будет служать запросы приложение.
Выяснилось, что демон puma не умееть reload-ить свой state. Для исправления
пришлось добавить строчку ExecReload=/bin/kill -USR2 $MAINPID
, чтобы он
перечитывал свой конфиг. Но т.к. у нас адрес БД хранится не в конфиге, а
задается переменной окружения, то сервис придется все равно перестартовывать.
Плюс пришлось добавить handler systemd reload
, чтобы вызывалась команда
systemctl daemon-reload
при изменении файла puma.service
.
В остальном все прошло успешно:
ansible-playbook reddit_app_one_play.yml --limit db --tags db-tag --check
ansible-playbook reddit_app_one_play.yml --limit db --tags db-tag
ansible-playbook reddit_app_one_play.yml --limit app --tags app-tag --check
ansible-playbook reddit_app_one_play.yml --limit app --tags app-tag
ansible-playbook reddit_app_one_play.yml --limit app --tags deploy-tag --check
ansible-playbook reddit_app_one_play.yml --limit app --tags deploy-tag
Думал, как проверить изменения для deploy-я, но ветка в гите у нас одна и она уже есть в составе образа.
reddit_app_multiple_plays.yml
, в котором сценарии могут применять лишь на конкретные хосты
ansible-playbook reddit_app_multiple_plays.yml --tags db-tag --check
ansible-playbook reddit_app_multiple_plays.yml --tags db-tag
ansible-playbook reddit_app_multiple_plays.yml --tags app-tag --check
ansible-playbook reddit_app_multiple_plays.yml --tags app-tag
ansible-playbook reddit_app_multiple_plays.yml --tags deploy-tag --check
ansible-playbook reddit_app_multiple_plays.yml --tags deploy-tag
site.yml
, в котором импортировали три других шаблона:app.yml
,db.yml
иdeploy.yml
.
ansible-playbook site.yml --check
ansible-playbook site.yml
Здесь мы уже не указываем теги и хосты, на которые применять конфигруацию.
Так уж получилось, что я сделал его в предыдущем ДЗ10 - смотреть п. ДЗ-10 UPD
Здесь пришлось повозиться. Сначало пришлось добавить правило фаервола в GCE,
которое разрешает коннект на ssh порт для машин с сетевым тегом packer-ssh
,
т.к. предыдущее дефолтовое правило успешно удалилось вместе с
terraform destroy
. Для образов был добавлен соответствующий тег.
Чтобы можно было запускать сборку из каталога packer
был добавлен symlink на
каталог ansible
.
В процессе создания образа для app
решил попробовать указать список пакетов
списком без использования циклов, и это работает. Работу с циклами также проверил.
В playbook-и app.yml
, db.yml
, deploy.yml
добавил маски имен GCE хостов,
т.к. перебивать Ip адреса в статическом inventory надоело ;).
ansible-playbook -i gce.py site.yml --check
ansible-playbook -i gce.py site.yml
Проверил работу приложения - посты создаются.
Все сделано по описанию - установлен ansible с помощью файла requirements.txt
sudo pip install -r requirements.txt
Далее было поднято тестовое откружение, созданы inventory файлы ( ini и yml), проверен ping до хостов.
На работе использую saltstack, все очень похоже - там тоже есть группы хостов, но нет возможности вложенности групп одну в другую.
Создан inventory файл в формате json inventory.json
UPD. не правильно понял задание (выяснилось на 11-й лекции ;)
Что было сделано - установлен пакет apache-libcloud
sudo pip install apache-libcloud
Далее создана временная папка, в нее скопирован репозиторий ansible с github-а
git clone https://github.com/ansible/ansible
Далее из этого репозитория скопировано 2 файла:
ansible/contrib/inventory/gce.py
и ansible/contrib/inventory/gce.ini
.
Далее в настройках GCE был создан service account с именем ansible
и скачан
его json-файл. Отредактирован файл gce.ini
- в нем указаны настройки для
подключения к GCE.
Проверяем настройки подключения к GCE:
./gce.py --list
Появляется список того, что у нас есть в проекте GCE в формате json. Далее пытаемся сделать ping виртуальных машин.
➜ ansible git:(ansible-1) ✗ ansible -i gce.py all -m ping
reddit-db | SUCCESS => {
"changed": false,
"ping": "pong"
}
reddit-app-1 | SUCCESS => {
"changed": false,
"ping": "pong"
}
Все выше указанные файлы лежат в каталоге ansible
за исключением gce.ini
и
key.json
(по соображениям безопасности).
Надеюсь сейчас я правильно понял ДЗ ;)
UPD 2. Видимо все такие нет ;) Установил старую версию ansible
git clone -b stable-2.3 --recursive https://github.com/ansible/ansible.git
source ansible/hacking/env-setup
проверяем версию
➜ ansible git:(stable-2.3) ansible --version
ansible 2.3.3.0 (stable-2.3 2c116617de) last updated 2018/01/13 09:33:33 (GMT +300)
config file =
configured module search path = Default w/o overrides
python version = 2.7.12 (default, Nov 20 2017, 18:23:56) [GCC 5.4.0 20160609]
проверяем ping
➜ ansible git:(ansible-1) ansible -i inventory.json dbserver -m ping
dbserver | SUCCESS => {
"changed": false,
"ping": "pong"
}
UPD 3. Добавил скрипт ansiblejson.py
, форматирущий файл с параметрами
inventory20.json
- это работает на версии ansible
< 2.1
.
При выполении команд, тоже наблюдается схожесть со saltstack-ом.
По аналогии в shell
и command
, там есть state cmd
с функциями exec_code
и run
.
При использовании git
модуля пришлось задать другую папку в параметре dest
,
чтобы результат был changed=true
. Хотя в обоих случаях модуль возвращает SUCCESS
. Если же использовать command
, то любой rc != 0, трактуется как
ошибка.
Вообщем понятен смысл и преимущество использования различных модулей вместо
вызова модуля command
с аргументами.
Создано 2 семейства образов из образа ubuntu-1604-lts
:
reddit-app-base
- базовый образ для приложения (с ruby на борту)
reddit-db-base
- базовый образ для БД (c mongod на борту)
В процессе создания instance-ов появилась ошибка "only 1 static address allowed". Вспомнил, что ранее назначали статиеский IP для машины bastion. Убрал из нее и все заработало.
В модуль app
перенес шаблон puma.service.tpl
из предыдущего задания.
В процессе выснили, как ссылаться на файлы внутри модуля по относительным путям (${path.module})
Был создан модуль vpc
. При создании ставится приоритет 1000 и ресурс пересоздается,
т.к. по-умолчанию у правил гугла прироритет 65534.
Также доработал свой модуль из предыдущего ДЗ для создания ssh ключей на уровне проекта.
source_range | результат |
---|---|
0.0.0.0 | пускает |
my-ip | пускает |
not-my-ip | не пускает |
При разбиении структуры на stage
и prod
в каждой их них пришлось делать
terraform get && terraform init
При попытке параллельного изменения возникает ошибка
Error: Error loading state: writing "gs://andywow/terraform/state/default.tflock" failed: googleapi: Error 412: Precondition Failed, conditionNotMet
Lock Info:
ID: 7283473f-73a1-2405-40c8-03fdfbaa90dd
Path:
Operation: OperationTypeApply
Who: andy@andyvm
Version: 0.11.1
Created: 2018-01-10 17:06:00.680041135 +0000 UTC
Info:
Предварительно создал backend в GCS. Далее описал GCS в файле main.tf. Сделал бэкап текущего state-файла.
Выполнил инициализацию в папке prod terraform init -backend-config=backend.conf
Пример файла backend.conf
- backend.conf.example
Было предложение перенести state из локального хранилица в gcs. Перенес.
Локальный terraform.tfstate
исчез с диска.
Проверил terraform plan
- конфигруация не поменялась, значит работает.
Перешел в папку prod
. Выполнил ту же команду, но на вопрос об загрузке state-а
ответил, что надо брать из удаленного хранилища.
Проверил terraform plan
- предложил сменить только IP-адрес для SSH-правила
фаервола, значит работает. Попробовал terraform apply
. Изменения применились.
Файлы deploy.sh и puma.service.tpl перенесены.
Файл puma.service.tpl
изменен - помимо порта, из предыдущего ДЗ, в шаблон
добавлена также переменная окружения DATABASE_URL
, указывающая на адрес БД.
В outputs.tf добавлена output db_internal_ip
,
указывающая на внутренний адрес хоста с БД.
Данная output передается на вход модулю app
для указания адреса хоста БД.
Т.к. БД стартует, по-умолчанию, на адресе 127.0.0.1
, пришлось добавить inline
провиженер для модуля db
в файл main.tf,
т.к. неправильно на мой взгляд, делать отдельный image для данной задачи
(безопасность).
В провиженере делается смена адреса и рестарт сервиса.
Потом также заморочился с портом БД.
Ну и добавим параметр порта к правилу фаервола (db_port
)
Решил сделать отдельное модуль для балансировщика. Пришлось добавить 2 параметра:
app_instance_count
- кол-во эксземпляров инстансаapp
(по-умолчанию,1
)create_loadbalancer
- создавать или нет балансировщик (по-умолчанию,false
)
Если создается балансировщик, то статические ip для инстансов не выделяются.
Если балансировщик не создается, то статические выделенные ip создаются, но этот
кейс я проверил только с app_instance_count = 1
, т.к. аккаунт GC бесплатный.
В процессе так же выяснил, что забыл указать session_affinity
для балансировщика
в предыдущем ДЗ, в результате чего постоянно кидало на разные хосты.
Код вынес в отдельную папку code
в папках stage
и prod
сделал симлинки на
нее. Для переопределния перменной ssh_source_ranges
добавил отдельный файл
custom.auto.tfvars
, из которого переменная подгружается автоматически.
Добавил модуль для работы с реестром модулей. Создал 2 бакета. При изменении имен бакетов, старые удаляются, новые создаются.
Name | Req | Default value | Description |
---|---|---|---|
app_port | N | 9292 | Port for puma service |
disk_image | N | reddit-base | The name of the image family to which the resulting image belongs |
private_key_path | Y | private key path (local machine) | |
project | Y | The project ID that will be used to launch instances and store images | |
public_key_path | Y | public key path (local machine) | |
zone | N | europe-west1-c | zone name |
В процессе работы сделал шаблон puma.service.tpl для параметризации порта. Потребовалось еще раз выполнить команду
terraform init
для того, чтобы terraform подкачал провайдера.
Команда работает, но для себя поставил в IDE Atom плагины:
atom-beautify - автоформатирование, поддерживает кучу синтаксисов
language-terraform - удобная подсветка кода и автодополнения для terraform
5. Создан файл terraform.tfvars.example
Создан модуль для terraform userkeymodule, отвечающий за динамическое формирование ключей.
Добавлена переменная users_public_keys
типа map
для задания пользователей и их ключей в формате user:public_key_path
При добавлении еще одного пользователя c ключем в список, через переменную, он появляется в списке ssh-ключей
При добавлении пользователя руками, а затем выполнение команды terraform apply
, пользователь, добавленный руками, удаляется. Т.е. все ssh-ключи проекта перезаписываются.
Создан балансировщик ;) Количество эксземпляров указывается в переменной instance_count
(по-умолчанию, 2).
Сделал проверку - запустил 2 экземпляра, посмотрел, что на один идет трафик, остановил его, трафик пошел на второй.
Не сразу понял, что балансировщику требуется время на запуск. Потом вспомнил, что об этом в лекции говорили.
Используется шаблон ubuntu16.json Параметры для вылидации и сборки передаются как аргумента командной строки:
packer validate \
-var 'gc_project_id=windy-skyline-188819' \
-var 'gc_source_image_family=ubuntu-1604-lts' \
ubuntu16.json
packer build \
-var 'gc_project_id=windy-skyline-188819' \
-var 'gc_source_image_family=ubuntu-1604-lts' \
ubuntu16.json
Параметры передаются в файле параметров:
packer validate -var-file=ubuntu16.vars.json ubuntu16.json
packer build -var-file=ubuntu16.vars.json ubuntu16.json
Name | Req | Default value | Description |
---|---|---|---|
gc_machine_type | N | f1-micro | machine type |
gc_disk_size | N | 10 | disk size (gb) |
gc_disk_type | N | pd-standard | disk type (pd-ssd / pd-standard) |
gc_image_description | N | long descr | image description |
gc_image_label_ruby_ver | N | 2-3-0 | ruby version (oonly label for image) |
gc_image_label_mongod_ver | N | 3-2-18 | mongod version (only label for image) |
gc_network | N | default | network name |
gc_preemptible | N | true | preemptible status of VM |
gc_project_id | Y | null | The project ID that will be used to launch instances and store images |
gc_source_image_family | Y | ubuntu-1604-lts | The name of the image family to which the resulting image belongs |
gz_zone | N | europe-west1-c | The zone in which to launch the instance used to create the image |
- Параметр "tag" убрал из описания, т.к. он присываивается instance-у только в момент создания образа, а далее не указыватеся
- Добавил параметр "preemptible", для выключения VM, в случае, если оставил ее включенной (образ ubuntu16.json используется только для сборки)
- В образ добавляются метки с версиями ruby и mongod
Используется шаблон immutablejson, который создает образ на базе образа reddit-base, созданного в предыдущем пункте.
Параметры для вылидации и сборки передаются как аргумента командной строки:
packer validate \
-var 'gc_project_id=windy-skyline-188819' \
immutable.json
packer build \
-var 'gc_project_id=windy-skyline-188819' \
immutable.json
Параметры передаются в файле параметров:
packer validate -var-file=immutable.vars.json immutable.json
packer build -var-file=immutable.vars.json immutable.json
Из списка параметров убраны параметры
- gc_source_image_family (т.к. мы завязаны на семейство образов reddit-base)
- gc_image_label_ruby_ver (здесь мы не устанавливаем ruby)
- gc_image_label_mongod_ver (здесь мы не устанавливаем mongod)
Запуск reddit app производится через systemd-service unit puma.service
Информация о строке запуска находится в файле create-reddit-vm.sh
./runner.sh <command_file.txt> - read and executes commands from file with checks
./ruby.txt - ruby install commands
./mongodb.txt - mongodb install commands
./deploy.txt - deploy app install commands
Запуск из удаленного репозитория В качестве параметра передается файл со списком команд для выполнения
gcloud compute instances create reddit-app \
--boot-disk-size=10GB \
--image-family ubuntu-1604-lts \
--image-project=ubuntu-os-cloud \
--machine-type=g1-small \
--tags puma-server \
--restart-on-failure \
--metadata startup-script-url="https://raw.githubusercontent.com/Otus-DevOps-2017-11/andywow_infra/master/runner.sh",\
cmdlist="https://raw.githubusercontent.com/Otus-DevOps-2017-11/andywow_infra/master/startup.txt"
Запуск с локальной системы:
gcloud compute instances create reddit-app \
--boot-disk-size=10GB \
--image-family ubuntu-1604-lts \
--image-project=ubuntu-os-cloud \
--machine-type=g1-small \
--tags puma-server \
--restart-on-failure \
--metadata-from-file startup-script="projects/andywow_infra/runner.sh" \
--metadata cmdlist="https://raw.githubusercontent.com/Otus-DevOps-2017-11/andywow_infra/master/startup.txt"
подключение к someinternalhost (1 команда):
ssh -i ~/.ssh/appuser -A -o ProxyCommand="ssh -W %h:%p %[email protected]" appuser@someinternalhost
либо, если версия ssh новее (проверял на bash for win ;)
ssh -i ~/.ssh/appuser -J [email protected] appuser@someinternalhost
доп. задание: В каталоге ~/.ssh создать файл config с правами 600 и следующим содержимым
Host someinternalhost
HostName someinternalhost
IdentityFile ~/.ssh/appuser
User appuser
# old version
ProxyCommand ssh -A -W %h:%p %[email protected]
# new version
# ProxyJump %[email protected]
Host bastion, EXTERNAL IP: 35.205.18.133, INTERNAL IP: 10.132.0.2 Host someinternalhost, INTERNAL IP: 10.132.0.3