monahdi Infra repository
Created two hosts. The first is bastion host has a public ip, and the second is someinternalhost has no public ip. Goinng to someinternalhost, need to connect VPN at bastion
openvpn cloud-bastion.ovpn
and then connecting to someinternalhost
ssh -i ~/.ssh/<username> <username>@< IP someinternalhost>
Connecting data:
bastion_IP = 34.77.141.228
someinternalhost_IP = 10.132.0.3
Наконец-то установил русский язык на виртуалку с Ubuntu, можно писать ридми на великом и могучем! Данные для автопроверки ДЗ:
testapp_IP = 35.233.3.109
testapp_port = 9292
Так вот, могучее ДЗ4. Создали инстанс через SDK GCP, командой из предоставленного к ДЗ листинга:
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
Далее, необходимо поставить руби. Используем скрипт:
#!/bin/bash
# Ruby install
sudo apt update
sudo apt install -y ruby-full ruby-bundler build-essential
БД будет Монго, ставим скриптом:
#!/bin/bash
# Install and start 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
И, наконец, качаем и врубаем приложение скриптом:
#!/bin/bash
# Dowload and start application
git clone -b monolith https://github.com/express42/reddit.git
cd reddit && bundle install
puma -d
ps aux | grep puma
Startup script для авторазвертывания приложения на создаваемом инстансе:
#!/bin/bash
# This is startup skript. It install ruby, MongoDB and deploy puma-app
#First to install ruby
sudo apt update
sudo apt install -y ruby-full ruby-bundler build-essential
#Second to 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
#End to deploy application
git clone -b monolith https://github.com/express42/reddit.git
cd reddit && bundle install
puma -d
ps aux | grep puma
Используемая команда gloud:
gcloud compute instances create reddit-app1 --scopes storage-ro \
--metadata startup-script-url=gs://my-first-startupscript/startup.sh\
--boot-disk-size=10GB \
--image-family ubuntu-1604-lts \
--image-project=ubuntu-os-cloud \
--machine-type=g1-small \
--tags puma-server \
--restart-on-failure
Команда для создания правила брендмаура:
gcloud compute firewall-rules create default-puma-server \
--network default\
--priority 1000\
--direction ingress\
--action allow\
--target-tags puma-server\
--source-ranges 0.0.0.0/0 \
--rules tcp:9292
Установили Packer. На линукса можно поставить следующим путем: сначала
$ mkdir -p $(go env GOPATH)/src/github.com/hashicorp && cd $_
$ git clone https://github.com/hashicorp/packer.git
$ cd packer
затем, переносим бинарный файл в bin и на последок делаем
make dev
Проверить работоспособность можно либо
packer -v
либо просто
packer
Далее, поставили ADC
$ gcloud auth application-default login
и сделали первый шаблон для Packer. Поначалу, шаблон состоял только из секции build следующего вида
{
"builders": [
{
"type": "googlecompute",
"project_id": "infra-123456",
"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",
"script": "scripts/install_ruby.sh",
"execute_command": "sudo {{.Path}}"
},
{
"type": "shell",
"script": "scripts/install_mongodb.sh",
"execute_command": "sudo {{.Path}}"
}
И наконе проверели работоспособность получившегося образа. Для начала проверка на ошибки
$ packer validate ./ubuntu16.json
ошибок не выявила, по этому смело запустили билд
$ packer build ubuntu16.json
Все прекрасно взлетело. Сдеплоили приложение, создав ВМ из получившегося образа, к которому подключились по ssh и поставили приложение из предыдущего ДЗ
$ git clone -b monolith https://github.com/express42/reddit.git
$ cd reddit && bundle install
$ puma -d
Приложение прекрасно взлетело. Далее, добавили пару переменных в .json
"variables": {
"project_id": "",
"source_image_family": "",
"machine_type": ""
}
поместили их значения в json-файл variables, который находится в .gitignore.Пример файла со значением переменных называется variables.json.example. Кроме того, добавили пару параметров build в конечную версию образа
"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-d",
"ssh_username": "appuser",
"machine_type": "{{user `machine_type`}}",
"image_description": "This is test description",
"disk_size": "50",
"disk_type": "pd-standard",
"network": "default",
"tags": "luma-server"
}
Получил инфу для выполнения доп.задания, попробую сделать после изучения)
Прекрасная штука Terraform. Удаляем через веб-морду GCP ключи, что ьбы чуть позже добавить их в метаданные машины через Терру. Качаем Терру, в моем случае tar архив, распаковываем и добавляем бинарник в PATH. Так как с ДЗ затянул, читая чатик и учась на чужих граблях, сразу ставлю версию 0.11.1) Проверяем установку командой
terraform -v
Создаем файл main.tf, который и будет главным конфигурационным файлом. Предусмотрительно сразу в .gitignore добавляем все файлы Терры, которые не желательно светить в публичном репозитории
*.tfstate
*.tfstate.*.backup
*.tfstate.backup
*.tfvars
.terraform/
Далее, в секции провайдеров определяем GCP
terraform {
# Версия terraform
required_version = "0.11.1"
}
provider "google" {
# Версия провайдера
version = "2.0.0"
# ID проекта
project = "$my_project_ID"
region = "$Region"
}
Грузим провайдер с помощью
terraform init
В конфигурационный фал добавляем ресурс для VM
resource "google_compute_instance" "app" {
name = "reddit-app"
machine_type = "g1-small"
zone = "europe-west1-b"
# определение загрузочного диска
boot_disk {
initialize_params {
image = "reddit-base"
}
}
# определение сетевого интерфейса
network_interface {
# сеть, к которой присоединить данный интерфейс
network = "default"
# использовать ephemeral IP для доступа из Интернет
access_config {}
}
}
Первый раз используем команду для планирования изменений
terraform plan
И отправляем Терру разворачивать ВМ, предварительно задав аргумент автоподтверждения
terraform apply -auto-approve=true
После выполнения команды появляется файл с описанием состояния terraform.tfstate Несколькими вариантами пыаемся найти в .state-файле внешний адрес ВМ, сначала руками, потом консрукцией
terraform show | grep nat_ip
Однако, более правильный путь - это определить для внешнего адреса output-переменную. Создаем отдельный файл outputs.tf, в котором делается следующая запись
output "app_external_ip" {
value=$google_compute_instance.app.network_interface.0.access_config.0.assigned_nat_ip}"
}
Далее делаем
terraform refresh
что бы output-переменная приняла значение, и... сталкиваемся с первой опечаткой в методичке) Корректируем файл outputs, выставив у атрибутов ресурса просто nat_ip вмсето assignet_nat_ip. Снова
terraform refresh
переменная принимает значение, которое смотрим с помощью
terraform output
Переменную для определения адреса задали, однако, подключится по ssh к ВМ не удается...Так как удалены ключи в начале упражнения. Задаем ключи в конфигурационном файле через метаданные
metadata {
# путь до публичного ключа
ssh-keys = "appuser:${file(public_key-path)}"
}
Прогоняем эти изменения через plan и apply. Теперь к ВМ можно подключитсья по ssh. Добавляем в конфигурационный файл ресурс правила для фаервола
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"]
}
Планируем и применяем изменения через уже полюбившуюся последовательность plan + apply. Внезапно, правило применяется только к ВМ с тегом. Добавляем в ресурс ВМ нужный тег
tags = ["reddit-app"]
И снова планируем и применяем изменения. Пора поиграться с провиженерами. Создаем директорию files. В нее кладем файл 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
А также файлик deploy.sh с
#!/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
Теперь можно забабахать провиженер в конфигурационный файл, который скоприует файл puma.service
provisioner "file" {
source = "files/puma.service"
destination = "/tmp/puma.service"
}
Еще один, который запустит скрипт деплоя
provisioner "remote-exec" {
script = "files/deploy.sh"
}
А также, определить параметры подключения провижеонеров, забив перед ними коннектор
connection {
type = "ssh"
user = "appuser"
agent = false
# путь до приватного ключа
private_key = "${file("~/.ssh/appuser")}"
}
Чтобы проверить работу провижионеров, нужно пересоздать ВМ. Сделать это можно попросив Терру при следующем apply пересоздать ВМ
terraform taint google_compute_instance.app
Снова планируем и применяем изменения. Смотрим, как взлетела пума. Далее, разбираемся с input-переменными. Создаем файл variables.tf для описания переменных. Выглядит он примерно так
variable project {
description = "Project ID"
}
variable region {
description = "Region"
# Значение по умолчанию
default = "europe-west1"
}
variable public_key_path {
# Описание переменной
description = "Path to the public key used for ssh access"
}
variable disk_image {
description = "Disk image"
}
В конфигурационном файле переменные определяются через синтаксис
"${var.var_name}"
Меняем несколько параметров в конфигурационном файле на переменные
provider "google" {
version = "2.0.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.tfvars Создаем его с примерно такого вида
project = "infra-179015"
public_key_path = "~/.ssh/appuser.pub"
disk_image = "reddit-base"
Пересоздаем все ресурсы с помощью последовательности команд
terraform destroy
terraform plan
terraform apply
Сталкиваюсь со своей опечаткой в конфиг файле, переименовываю переменную из publick_key_pass в public_key-pass. Снова дестрой, взлет, поехали)
Далее, добавляем в описание переменных (файл variables.tf) переменные для коннектора ключа провижионеров и переменную для зоны. В переменную для зоны задаю параметр по умолчанию. Форматирую файлы конфигурации командой
terraform fmt
и создаю файл с примером определения переменных terraform.tfvars.example. Задания со звездочкой приходится оставлять на потом, ибо есть уже долги...
Подправил несколько опечаток в readmy и надо бы это сделать покомпактнее... Добавляем в конфиг-файл main.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"]
}
Для того, что бы Терраформ знал о том, что правило уже существует, импортируем его из уже развернутой ифнраструктуры командой
$ terraform import google_compute_firewall.firewall_ssh default-allow-ssh
Задаем адрес для инстанса приложения как внешний ресурс
resource "google_compute_address" "app_ip" {
name = "reddit-app-ip"
}
после чего удаляем зарезервированный под бастион адрес, ибо бесплатная версия GCP... Меняем конфигурацию сетевого инфтерфейса для инстанса, что бы воспользоваться тем самым адресом
access_config = {
nat_ip = "${google_compute_address.app_ip.address}"
При пересоздании ресурсов видим, что Терра сначала создает ресурс с адресом, а уже потом начинает созадавть инстанс. Это прямая зависимость. Создаем через Packer два новых образа, отдельно для БД и приложения. Разбиваем конфиг-файл Терры на два - для приложения и для БД. Также, правило для доступа по ssh выносим в отедльный файл-конфиг. В итоге в main-файле остатется только конфигурация провайдера. Все это было подготовкой к использованию модулей. Далее создаем отдельные дирректории для модуля прилодения и модуля БД. В каждом из них свой конфиг-файл и файлы переменных. В файле main-конфига остаются только секции для вызова модулей
provider "google" {
version = "2.0.0"
project = "${var.project}"
region = "${var.region}"
}
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 = "${var.db_disk_image}"
}
Для того, что бы начать использовать модули, запускаем командой
terraform get
их загрузку. В основном файле выходных переменных меняем значение на
output "app_external_ip" {value = "${module.app.app_external_ip}"
}
дабы получить значение, а не ссылаться на уже не существующий ресурс. Далле, по анологии создаем модуль vpc, который по сути копирует созданный ранее файл vpc.tf. С помощью небольшой параметризации данного модуля, задаем значение адреса как диапазон. Теперь в основном конфиг-файле можно задать sourceRange равный своему IP-адресу. В этом случае подключаться к создаваемым инстансам можно будет только с одного адреса. Создаем два окружения, stage и prod, на основе основного файла конфигурации) Отличаются эти два окружения только уровнем доступа к инстансам по ssh. В каждом делаем
terraform init
terraform apply
terraform destroy
для проверки работоспособности. Приводим конфиг=файлы к божескому виду с помощью
terramorm fmt
Сносим основной конфиг и на его месте делаем конфиг для двух бакетов
provider "google" {
version = "2.0.0"
project = "${var.project}"
region = "${var.region}"
}
module "storage-bucket" {
source = "SweetOps/storage-bucket/google"
version = "0.1.1"
name = ["storage-bucket-firsttestbacket", "storage-bucket-secondtestbacked"]
}
output storage-bucket_url {
value = "${module.storage-bucket.url}"
}
Инициируем Терру, проверяем создание бакетов и все дестроим. Доп задания как обычно, с надеждой на лучшие времена...
С горем пополам (ибо много безуспешных попыток поставить Ansible версии 2.4 ни к чему не привели), ставим Ansible. ПОднимаем stage-инфру Терраформом, после чего проводим пару упражнений. Создаем инвентори файл с простым описанием одной машины
appserver ansible_host=35.195.186.154 ansible_user=appuser \
ansible_private_key_file=~/.ssh/appuser
И пингуем его
ansible appserver -i ./inventory -m ping
Ту же процедуру повторяемдля ВМ с БД. Далее, создаем простой конфиг-файл
[defaults]
inventory = ./inventory
remote_user = appuser
private_key_file = ~/.ssh/appuser
host_key_checking = False
retry_files_enabled = False
И теперь меняем файл инвентори на более простой
appserver ansible_host=35.195.74.54
dbserver ansible_host=35.195.162.174
Продолжаем упражняться с модулем command, который позволяет запускать произвольные команды
ansible dbserver -m command -a uptime
после чего создаем пару групп хостов в инвентори для удобства
[app] #Это название группы
appserver ansible_host=35.195.74.54 #Cписок хостов в данной группе
[db]
dbserver ansible_host=35.195.162.174
и теперь модно отдавать команды на целую группу
ansible app -m ping
Дальше, переносим наш инвентори в формат YAML, переопределяем путь к инвентори и пингуем все машины
ansible all -m ping -i inventory.yml
Проверяем с помощью уже известного модуля command установленые на ВМ компоненты
$ ansible app -m command -a 'ruby -v'
$ ansible app -m command -a 'bundler -v'
Все круто, вот только command не запускает shell, по этому не может выполнять последовательности команд, например. Для этого используется модуль shell
$ ansible app -m shell -a 'ruby -v; bundler -v'
Упражняемся с проверкой статуса БД. Можно это сделать с помощью модулей command и shell
$ ansible db -m command -a 'systemctl status mongod'
$ ansible db -m shell -a 'systemctl status mongod'
Однако, куда интереснее выводы systemd
$ ansible db -m systemd -a name=mongod
и service
$ ansible db -m service -a name=mongod
Модули возвращают набор переменных, которые можно использовать в последующем коде. Дальще, различными способами пытаемся снова склонировать git,который уже есть на серверах. Команды проходят, но changed возвращается с false, что означает отсутсвие изменений. Для интереса, реализуем простой плейбук с той же целью
- name: Clone
hosts: app
tasks:
- name: Clone repo
git:
repo: https://github.com/express42/reddit.git
dest: /home/appuser/reddit
запускаем его
ansible-playbook clone.yml
и снова не получаем изменений. Для практики, сноси дирректорию с git командой
ansible app -m command -a 'rm -rf ~/reddit'
после чего повторный запуск плейбука возвращается с изменениями на appserver.
Создаем единый плейбук для управления и деплоя нашего приложения. Начинаем содного сценария на плейбук. Перво наперво указываем, для какой группы хостов будет действовать плейбук:
- 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
Создаем шаблон конфига для таска выше
# 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 }}
После чего можно прогнать наш сценарий. Для пробного прогона используется ключ --check, аналог terraform plan. Также указываем тег через ключ --limit, что бы запустить определенный таск
ansible-playbook reddit_app.yml --check --limit db
После ошибки, задаем в плейбуке переменную, которую использовали в шаблоне, но не объявили
- 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
После чего пробный прогон проходит успешно и мы можем запустить плейбук, увидив изменения, которые он произведет. Добавляем в плейбук handlers - таск, который вызывается только при оповещении от других тасков. В нашем случае мы хотим, что бы БД преезапускалась после отработки нашим таском.
- 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
Применяем плейбук. Далее, создаем юнит-файл для будущего переноса на сервер приложения
[Unit]
Description=Puma HTTP Server
After=network.target
[Service]
Type=simple
EnvironmentFile=/home/appuser/db_config
User=appuser
WorkingDirectory=/home/appuser/reddit
ExecStart=/bin/bash -lc 'puma'
Restart=always
[Install]
WantedBy=multi-user.target
В юнит-файле есть строка для чтения переменных. Создаем файл для этого со следующим содержанием
DATABASE_URL={{ db_host }}
Добавляем в сценарий переменную адреса сервера (внутренний адрес базы). Добавляем таск для копирования файла (модуль copy) и автозапуска приложения (модуль systemd). В таски также добавляем задачу для копирования созданного ранее шаблона с переменой адреса. Кроме того, добавляем handlers, указывающий на изменения юнит-файла и отправляющий его перечитывать
- name: Configure hosts & deploy application
hosts: all
vars:
mongo_bind_ip: 0.0.0.0
db_host: 10.132.0.2
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
- 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: 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
handlers:
- name: restart mongod
become: true
service: name=mongod state=restarted
- name: restart mongod
become: true
service: name=mongod state=restarted
- name: reload puma
become: true
systemd: name=puma state=restarted
Делаем пробный прогон плейбука и применяем его к хостам app (только сценарии app-tag)
$ ansible-playbook reddit_app.yml --check --limit app --tags app-tag
$ ansible-playbook reddit_app.yml --limit app --tags app-tag
Переходим к деплою. Добавляем через модули git и bundl клонирование актуальной репы приложения и установку рубей
tasks:
...
- 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
Все круто, на каждый раз для запуска нужных сценариев нужно указывать необходимые теги. Не очень удобно и для решения этого вопроса предлагается разбить большой сценарий на несколько поменьше, каждый со своей ролью. Сценарий для БД
- 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
Сценарий для инстанса приложения
- 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
- 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
И сценарий для деплоя. Итоговый сценарий
- 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
- 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=reloaded
- 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 db-tag --check
$ ansible-playbook reddit_app2.yml --tags db-tag
$ ansible-playbook reddit_app2.yml --tags app-tag --check
$ ansible-playbook reddit_app2.yml --tags app-tag
$ ansible-playbook reddit_app2.yml --tags deploy-tag --check
$ ansible-playbook reddit_app2.yml --tags deploy-tag
Стало удобнее, но не на много. По этому для большего удобства...создаем несколько плейбуков) Также, как и по сценариям, по трем ролям: приложение, БД, деплой. Убираем из сценариев теги и разносим по плейбукам. Для БД:
- 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
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
А теперь можно сделать "главный" плейбук, который будет включать все остальные
- import_playbook: db.yml
- import_playbook: app.yml
- import_playbook: deploy.yml
Пересоздаем инфру, меняем параметры в инвентори и погнали
$ ansible-playbook site.yml --check
$ ansible-playbook site.yml
И наконец то, что заняло три часа траблшутинга и ни к чему не привело) Меняем провиженеры в конфигурации Пакера со скриптов на плейбуки Ансибла. Плейбук для образа приложения
- name: Install Ruby
hosts: all
become: true
tasks:
- name: Update apt packages
apt:
update_cache: true
- name:
apt:
name: "{{packages}}"
state: present
vars:
packages:
- ruby-full
- ruby-bundler
- build-essential
и плейбук для образа БД
- name: Install DB
hosts: all
become: true
tasks:
- name: Install ppa key
apt_key:
keyserver: hkp://keyserver.ubuntu.com:80
id: EA312927
- name: Update apt packages
apt:
update_cache: true
- name: App repo
apt_repository:
repo: deb [ arch=amd64,arm64 ] http://repo.mongodb.org/apt/ubuntu xenial/mongodb-org/3.2 multiverse
state: present
- name: Install DB
apt:
name: mongodb-org
state: present
- name: Enable autostart DB
systemd:
name: mongod
enabled: yes
Меняем провижионеры в Пакере
"provisioners": [
{
"type": "ansible",
"playbook_file": "ansible/packer_app.yml"
}
]
и
"provisioners": [
{
"type": "ansible",
"playbook_file": "ansible/packer_db.yml"
}
]
Ключ force - для слабаков, по этому сносим имеющиеся образы и начинаем подымать те, что только что создали... И тут внезапно начинаются какие то сложности...Образ приложения поднимается при последней конфигурации, а вот БД ни в какую не хочет...Проверки Тревиса проходят, то есть гипотетически все корректно. Но образ удален и новый не поднимается) Отступать никуда...После трех часов мучений и помощи сообщества, в плейбук БД при установке базы был добавлен параметр
allow_unauthenticated: yes
так как в противном случае GCP по неведомым причинам считаем установку БД установкой из недоверенного источника...
###HW10 Продолжаем нарищаивать удобство) В этот раз начинаем с создания ролей. Инициируем создание ролей db и app с помощью команд
$ ansible-galaxy init app
$ ansible-galaxy init db
Получаем две папке со структурой, принятой в Ansible Galaxy. Копируем тас из плейбука app в main плейбук директории tasks роли db. В директорию шаблонов этой же роли переносим конфиг БД из директории ansible/templates. Так как такс автомотически проверит наличие шаблонов в директории роли, ссылки на них из такска можно убрать и написать просто:
# tasks file for db
- name: Change mongo config file
template:
src: mongod.conf.j2
dest: /etc/mongod.conf
mode: 0644
notify: restart mongod
Аналогично переносим хендлер из плейбука в роль
# handlers file for db
- name: restart mongod
service: name=mongod state=restarted
В секцию роли defaults переносим секцию переменных
# defaults file for db
mongo_port: 27017
mongo_bind_ip: 127.0.0.1
И повторяем анологичные действия с ролью app. В переменную в директории defaults вставляем адрес БД
db_host: 127.0.0.1
Теперь в плейбуках можно использовать роли и превести плейбук app.yml к виду
- name: Configure App
hosts: app
become: true
vars:
db_host: 10.132.0.2
roles:
- app
а плейбук БД к виду
- name: Configure DB
hosts: db
become: true
vars:
mongo_bind_ip: 0.0.0.0
roles:
- db
Дальше, тренируемся с окружениями. В Терраформе описано два, будем управлять каждым из Ansible. Создаем директорию environments, в ней две поддиректории - stage и prod. В каждую помещаем свой файл inventory (используемый ранее удаляем). Определяем окружением по умолчанию stage вот такой не хитрой правкой конфига
[defaults]
inventory = ./environments/stage/inventory
remote_user = appuser
private_key_file = ~/.ssh/appuser
host_key_checking = False
Дальше, в каждой из поддиректорий окружения создаем директорию group_vars, где будем создавать файлы для определения переменных для целых групп хостов. Файлы сии должны соответствовать названиям групп в инвентори-файле. Создаем в stage/group_vars файл app и переносим в него содержимое vars из плейбука app.yml
db_host: 10.132.0.2
при этом переменные из плейбука удаляем. Те же действия производим при создании там же файла db
mongo_bind_ip: 0.0.0.0
Так же создаем файл переменных all следующего содержания
env: stage
так как по умолчанию создастя группа all и для доступности переменных всем хостам, нужно указать окружение. Копируем все файлы group_vars из stage в prod, с изменением только в переменной all
env: prod
Для возможности будущего вывода информации об окружениях в переменных ролей добавляем строчку определения информации об окружении. Для /roles/app/defaults/main.yml
# defaults file for app
db_host: 127.0.0.1
env: local
Для /roles/db/defaults/main.yml
# defaults file for db
mongo_port: 27017
mongo_bind_ip: 127.0.0.1
env: local
И теперь в таск роли app и db добавляем
# tasks file for app
- name: Show info about the env this host belongs to
debug:
msg: "This host is in {{ env }} environment!!!"
Меняем структуру нашей директории ansible, что бы навести там порядок, а заодно корректируем конфиг следующим образом
[defaults]
inventory = ./environments/stage/inventory
remote_user = appuser
private_key_file = ~/.ssh/appuser
# Отключим проверку SSH Host-keys (поскольку они всегда разные для новых инстансов)
host_key_checking = False
# Отключим создание *.retry-файлов (они нечасто нужны, но мешаются под руками)
retry_files_enabled = False
# # Явно укажем расположение ролей (можно задать несколько путей через ; )
roles_path = ./roles
[diff]
# Включим обязательный вывод diff при наличии изменений и вывод 5 строк контекста
always = True
context = 5
Проверяем, все ли работает, пересобирая инфру и запуская плейбуки поочередно для stage и prod. Все работает, мы молодцы. Далее, учимся работать с комюнити-ролями на примере nginx. Добавляем файл requirements.yml в директории окружения stage и prod. Этой файлик для зависимостей ролей. Туда вписываем
- src: jdauphant.nginx
version: v2.21.1
Устанавливаем роль командой
ansible-galaxy install -r environments/stage/requirements.yml
Так как комитить комьюнити-роли плохая практика, в гитигнор добавляем jdauphant.nginx. В grop_vars/app добавляем
nginx_sites:
default:
- listen 80
- server_name "reddit"
- location / {
proxy_pass http://127.0.0.1:9292;
}
для минимальной настройки nginx. В конфиге Террафор открываем для прилоджения порт 80, внеся коррективы в модуль app/main.tf
resource "google_compute_firewall" "firewall_puma" {
name = "allow-puma-default"
network = "default"
allow {
protocol = "tcp", ports = ["9292"]
protocol = "tcp", ports = ["80"]
}
source_ranges = ["0.0.0.0/0"]
target_tags = ["reddit-app"]
Добавляем вызов роли в плейбук приложения
roles:
- app
- jdauphant.nginx
и, запустив общий плейбук, убеждаемся в том, что приложение доступно по порту 80. Немного поупражняемся с Ansible Vault. Создаем файл vault.key out-of-tree. В файл конфигурации добавляем опцию
[defaults]
...
vault_password_file = ~/.ansible/vault.key
Добавляем плейбук для создания пользователей 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 }}"
И для каждого окружения создаем файл credentials.yml с данными пользователей. Для prod
credentials:
users:
admin:
password: admin123
groups: sudo
для stage
credentials:
users:
admin:
password: qwerty123
groups: sudo
qauser:
password: test123
Бахаем шифрование
$ ansible-vault encrypt environments/prod/credentials.yml
$ ansible-vault encrypt environments/stage/credentials.yml
И убеждаемся в том, что все зашифровалось, а пользователи создались. В основной плейбук также добавляем вызов плейбука для создания пользователей
- import_playbook: db.yml
- import_playbook: app.yml
- import_playbook: deploy.yml
- import_playbook: users.yml
Для примера с локальной разработкой, используем Vagrant. Устанавливаем VirtualBox, который является одним из проивижионеров вагранта, а также сам вагрант. Создаем в директории с ансиблом файл Vagrantfile, в который добавляем описание двух виртуалок, аналогичных с теми, что в GCP создавали с помошью терраформа.
Vagrant.configure("2") do |config|
config.vm.provider :virtualbox do |v|
v.memory = 512
end
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"
end
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"
end
end
Поднимаем созданные виртуалки:
vagrant up
Командой vagrant box list можно посмотреть, какие боксы скачались на локальную машину, а командой vagrant status проверить статус того, какие ВМ запущены. Так как в дальнейшем используется Ансибл, добавляем его описание как провижионера в вагрантфайл. Файл теперь выглядит вот так:
Vagrant.configure("2") do |config|
config.vm.provider :virtualbox do |v|
v.memory = 512
end
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
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"
end
end
Если нужно применить провижионер прямо сейчас, а не при следующем запуске ВМ, можно воспользоваться командой
vagrant provision dbserver
Если команду запустить сейчас, применение произойдетс с ошибкой из-за отсутсвия питона. Что бы сделать все по красоте и добавить питон на все запускаемые в последующем машины, создаем еще один плейбук 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
По ходу дела удаляем из site.yml неиспользуемый в задание плейбук user.yml. Теперь провижионер применится, но с ошибкой об отсутсвии монги. Для установки монги, доабвляем в роль db таск install_mongo.yml
- name: Add APT key
apt_key:
id: "EA312927"
keyserver: keyserver.ubuntu.com
tags: install
- 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
tags: install
- name: Install mongodb package
apt:
name: mongodb-org
state: present
tags: install
- name: Configure service supervisor
systemd:
name: mongod
enabled: yes
state: started
tags: install
Такс по управлению БД так же выносим в отдельный файл config_mongo.yml
- name: Change mongo config file
template:
src: templates/mongod.conf.j2
dest: /etc/mongod.conf
mode: 0644
notify: restart mongod
И меняем файл main для вызова тасков в нужном порядке
- 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
Снова запускаем применение провижионера и теперь оно проходит успешно. Аналогично выносим в отдельные таски задания для роли app. Создаем файл 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.yml:
- 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
Опять же в файле main делаем вызов тасков в нужном порядке:
- name: Show info about the env this host belongs to
debug:
msg: "This host is in {{ env }} environment!!!"
- include: ruby.yml
- include: puma.yml
Добавляем в вагрантфайл провижионер для хоста приложения
Vagrant.configure("2") do |config|
config.vm.provider :virtualbox do |v|
v.memory = 512
end
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
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
end
Далее, добавляем переменную пользователя, ибо иначе провижионер будет вылетать с ошибкой созданиядиректории для настройки подключения к БД, ибо нет самого пользователя, и, соответсвенно, его директории. В файле main роли приложения доабвляем переменную пользователя
deploy_user: appuser
И начинаем менять таски. Для начала меняем таск пумы, где модуль copy меняем на template для возможности параметризации файла настройки, а также все упоминания о пользователе заменяем на переменную
---
- 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
И меняем сам файл. Переименовываем его из puma.service в puma.service.j2 и переносим из директории app/files в app/templates. Внутри файла все упоминания о пользователе меняем на переменные:
[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
Кроме того, упоминания о пользователе мы встречаем в deploy.yml, который вызывавется при запуске site.yml. Меняем и там значения на переменную
- name: Deploy App
hosts: app
vars:
deploy_user: appuser
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
Кроме того, в вагрант-файле нужно переопределить имя пользователя на то, которое по умолчянию использует бокс. Идя по граблям коллег, меняем его на vagrant:
Vagrant.configure("2") do |config|
config.vm.provider :virtualbox do |v|
v.memory = 512
end
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
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"}
}
ansible.extra_vars = {
"deploy_user" => "vagrant"
}
end
end
end
Применяем провиженинг и работу приложения, все работает. После удаляем конфигурацию и поднимаем ее снова, что бы убедиться, что все работает
vagrant destroy -f
vagrant up
С ошибкой вываливается руби. Добавляем в ruby.yml обновление пакетов
- name: Only run "update_cache=yes" if the last one is more than 3600 seconds ago
apt:
update_cache: yes
cache_valid_time: 3600
- name: Install ruby and rubygems and required packages
apt: "name={{ item }} state=present"
with_items:
- ruby-full
- ruby-bundler
- build-essential
tags: ruby
Теперь все работает. Далее, для тестирования ролей нам понадобится Molecule и Testinfra. Ставим их, добавив в requirements.txt следующие строки
molecule>=2.6
testinfra>=1.10
python-vagrant>=0.5.15
и запустив pip
pip install -r requirements.txt
Далее, в директории роли db инициируем создание заготовки тестов
molecule init scenario --scenario-name default -r db -d vagrant
Добавляем несколько тестов для примера в db/molecule/default/tests/test_default.py
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
Описание тестовой машины можно найти в db/molecule/default/molecule.yml, в нашем слуае ничео менять не нужно, т.к. уже используется нужный бокс. Создается ВМ для тестирования командой
molecule create
Посмотреть список созданых машин можно командой molecule list, после чего можно по SSH например провалиться внутрь ВМ по её имени. Молексула генерит плейбук для проверки роли здесь db/molecule/default/playbook.yml. В него добавляем параметр become и переменную адреса
- name: Converge
become: true
hosts: all
vars:
mongo_bind_ip: 0.0.0.0
roles:
- role: db
Применяем плейбук к созданному хосту и запускаем тест
molecule converge
molecule verify
Далее, добавляем к тесту проверку нужного БД порта
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
# check if db listning port 27017
def test_mongo_port(host):
assert host.socket("tcp://0.0.0.0:27017").is_listening
Прогоняем, работает. И на последок, в плейбуках пакера применяем роли. Для этого заменяем содержимое плейбуков на
- name: Provisioner for app image
hosts: all
become: yes
roles:
- app
и
- name: Provisioner for app image
hosts: all
become: yes
roles:
- db
Однако пакер благополучно не видит роли...Добавляем в .json путь к ролям
"ansible_env_vars": ["ANSIBLE_ROLES_PATH={{ pwd }}/ansible/roles"]
а к json прилодения еще и теги руби
"extra_arguments": ["--tags","ruby"]
Все взлетает)
Новую ветку ДЗ начинаем с установки докера. Упражняемся:
docker run - запуск контейнера
docker ps - список запущенных контейнеров
docker pa -a - список всех конетйнеров
docker image - просмотр имеющихся образов
docker start - запуск остановленного (уже созданного) контейнера
docker attach - подключает к контейнеру
docker run = docker create+docker start+docker attach
docker exec -it <u_container_id> bash - запуск процесса внутри контейнера
docker commit <u_container_id> yourname/ubuntu-tmp-file - создает образ из созданного контейнера (контейнер остается запущенным)
docker system df - отображает сколько дискового пространства
занято образами, контейнерами и volume’ами
docker rm - удаляет контейнер
docker rmi - удаляет образ
Ставим SDK и docker machine для удаленного развертывания контейнеров в GCP. Docker-machine создает хост для докер демона со указываемым образом в --google-machine-image. Создаем хост командой
docker-machine create --driver google --google-machine-image https://www.googleapis.com/compute/v1/projects/ubuntu-os-cloud/global/images/family/ubuntu-1604-lts --google-machine-type n1-standard-1 --google-zone europe-west1-b docker-host
Проверить запуск можно командой
docker-machine ls
Создаем несколько файлов для дальнейшей работы
#mongod.conf
# 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: 27017
bindIp: 127.0.0.1
#db_config
DATABASE_URL=127.0.0.1
#start.sh
#!/bin/bash
/usr/bin/mongod --fork --logpath /var/log/mongod.log --config /etc/mongodb.conf
source /reddit/db_config
cd /reddit && puma || exit
#Dockerfile
FROM ubuntu:16.04
RUN apt-get update
RUN apt-get install -y mongodb-server ruby-full ruby-dev build-essential git
RUN gem install bundler
RUN git clone -b monolith https://github.com/express42/reddit.git
COPY mongod.conf /etc/mongod.conf
COPY db_config /reddit/db_config
COPY start.sh /start.sh
RUN cd /reddit && bundle install
RUN chmod 0777 /start.sh
CMD ["/start.sh"]
Теперь можем собрать свой образ
docker build -t reddit:latest .
И запускам контейнер командой
docker run --name reddit -d --network=host reddit:latest
И настраоим фаервол
gcloud compute firewall-rules create reddit-app \
--allow tcp:9292 \
--target-tags=docker-machine \
--description="Allow PUMA connections" \
--direction=INGRESS
Приложка, запущенная в контейнере, работает. Далее, запушим наш образ в DockerHub
docker tag reddit:latest <your-login>/otus-reddit:1.0
И для интереса запускаем его на локальном хосте
docker run --name reddit -d -p 9292:9292 <your-login>/otus-reddit:1.0
Проверяем подключение к ранее созданному докер-хосту. Так как мы его сненсли для экономии средстве, стартуем его для начала занаво)
docker-machine start docker-host
Качаем архив заранее разбитого на микросервисы приложения, и создаем в каждой части Dockerfile:
./post-py/Dockerfile
FROM python:3.6.0-alpine
WORKDIR /app
ADD . /app
RUN pip install -r /app/requirements.txt
ENV POST_DATABASE_HOST post_db
ENV POST_DATABASE posts
CMD ["python3", "post_app.py"]
./comment/Dockerfile
FROM ruby:2.2
RUN apt-get update -qq && apt-get install -y build-essential
ENV APP_HOME /app
RUN mkdir $APP_HOME
WORKDIR $APP_HOME
ADD Gemfile* $APP_HOME/
RUN bundle install
COPY . $APP_HOME
ENV COMMENT_DATABASE_HOST comment_db
ENV COMMENT_DATABASE comments
CMD ["puma"]
./ui/Dockerfile
FROM ruby:2.2
RUN apt-get update -qq && apt-get install -y build-essential
ENV APP_HOME /app
RUN mkdir $APP_HOME
WORKDIR $APP_HOME
ADD Gemfile* $APP_HOME/
RUN bundle install
ADD . $APP_HOME
ENV POST_SERVICE_HOST post
ENV POST_SERVICE_PORT 5000
ENV COMMENT_SERVICE_HOST comment
ENV COMMENT_SERVICE_PORT 9292
CMD ["puma"]
Качаем последний образ монги
docker pull mongo:latest
и начинаем собирать образы для контейнеров
docker build -t <your-dockerhub-login>/post:1.0 ./post-py
выхватываем ошибку пипа. Ошибка решается сообществом, а именно добавление в начала докерфайла слоя
RUN apk add --update gcc python python-dev py-pip build-base
продолжаем собирать образы
docker build -t <your-dockerhub-login>/comment:1.0 ./comment
docker build -t <your-dockerhub-login>/ui:1.0 ./ui
Создаем отдельную сеть для приложения и запускаем контейнеры из собранных образов
docker network create reddit
docker run -d --network=reddit --network-alias=post_db --network-alias=comment_db mongo:latest
docker run -d --network=reddit --network-alias=post <your-dockerhub-login>/post:1.0
docker run -d --network=reddit --network-alias=comment <your-dockerhub-login>/comment:1.0
docker run -d --network=reddit -p 9292:9292 <your-dockerhub-login>/ui:1.0
Приложении работает, но внезапно оказывается, что образы весят как-то не оптимально много... Меняем докерфайл ui на следующего содержания
FROM ubuntu:16.04
RUN apt-get update \
&& apt-get install -y ruby-full ruby-dev build-essential \
&& gem install bundler --no-ri --no-rdoc
ENV APP_HOME /app
RUN mkdir $APP_HOME
WORKDIR $APP_HOME
ADD Gemfile* $APP_HOME/
RUN bundle install
ADD . $APP_HOME
ENV POST_SERVICE_HOST post
ENV POST_SERVICE_PORT 5000
ENV COMMENT_SERVICE_HOST comment
ENV COMMENT_SERVICE_PORT 9292
CMD ["puma"]
И пересобираем ui. Рещультат получше. Далее, грохаем контейнеры и собираем их занаво
docker kill $(docker ps -q)
docker run -d --network=reddit --network-alias=post_db --network-alias=comment_db mongo:latest
docker run -d --network=reddit --network-alias=post <your-dockerhub-login>/post:1.0
docker run -d --network=reddit --network-alias=comment <your-dockerhub-login>/comment:1.0
docker run -d --network=reddit -p 9292:9292 <your-dockerhub-login>/ui:2.0
Все вроде бы здорово, но при перезапуске приложение не сохраняет данные. Создадим волюм и повторим действия выше
docker volume create reddit_db
Теперь посты на сайте будут сохраняться даже при перезапуске контейнеров.
Как и всегда, начинаем с запуска остановленного докер хочта и перестановки сертификатов. Подключаемся к хосту. Упражняемся с запуском контейнеров c none-дравером.
docker run -ti --rm --network none joffotron/docker-net-tools -c ifconfig
Контейнер запускается, делает ifconfig и падает. Запускаем контейнер уже в сетевом пространстве хоста с целью посмотреть, что там выдаст конфиг
docker run -ti --rm --network host joffotron/docker-net-tools -c ifconfig
Картинка такая же, как если бахнуть на хосте ifconfig по ssh. Упражняемся с запуском контейнеров и просмотром их неймспейсов. Далее, создаем bridge-сеть (спойлер - она уже создана)
docker network create reddit --driver bridge
Запускаем контейнеры с использованием этой сети и приложка не работает...Запускаем контейнеры с присвоением сетевых алиасов при старте, что бы по ним ДНС докера знал о существовании контейнеров
docker run -d --network=reddit --network-alias=post_db --network-alias=comment_db mongo:latest
docker run -d --network=reddit --network-alias=post <your-login>/post:1.0
docker run -d --network=reddit --network-alias=comment <your-login>/comment:1.0
docker run -d --network=reddit -p 9292:9292 <your-login>/ui:1.0
Все взлетело. Тренируемся с сетью, разбивая запущенные контейнеры на две сети. Для начала грохаем старые контейнеры
docker kill $(docker ps -q)
и создаем сети
docker network create back_net --subnet=10.0.2.0/24
docker network create front_net --subnet=10.0.1.0/24
запускаем контейнеры уже в них
docker run -d --network=front_net -p 9292:9292 --name ui <your-login>/ui:1.0
docker run -d --network=back_net --name comment <your-login>/comment:1.0
docker run -d --network=back_net --name post <your-login>/post:1.0
docker run -d --network=back_net --name mongo_db --network-alias=post_db --network-alias=comment_db mongo:latest
Снова фейл. Теперь дело в том, что при инициализации контейнера докер подключает к нему только одну сеть. Контейнеры же post и comment для реализации с задумкой того, что ui не видит БД, нужно поместить в обе сети.
docker network connect front_net post
docker network connect front_net comment
Теперь все работает. Далее с помощью bridge-utils на докер-хосте рассматриваем логику работы сейтей докера. После этих упражнений, ставим docker compouse и создаем ямлик с описанием будущей конфиги
#docker-compose.yml
version: '3.3'
services:
post_db:
image: mongo:3.2
volumes:
- post_db:/data/db
networks:
- reddit
ui:
build: ./ui
image: ${USERNAME}/ui:1.0
ports:
- 9292:9292/tcp
networks:
- reddit
post:
build: ./post-py
image: ${USERNAME}/post:1.0
networks:
- reddit
comment:
build: ./comment
image: ${USERNAME}/comment:1.0
networks:
- reddit
volumes:
post_db:
networks:
reddit:
Грохаме все контейнеры, подставляем переменную с именем и - вауля - вся приложка взлетает одной командой
docker kill $(docker ps -q)
export USERNAME=<your-login>
docker-compose up -d
docker-compose ps
Меняем декларативное описание под наш кес с несколькими сетями и добавляем переменных
version: '3.3'
services:
post_db:
image: mongo:3.2
volumes:
- post_db:/data/db
networks:
- reddit
ui:
build: ./ui
image: ${USERNAME}/ui:${VERSION1}
ports:
- ${PORT}:${PORT}/tcp
networks:
- reddit
post:
build: ./post-py
image: ${USERNAME}/post:${VERSION2}
networks:
- reddit
comment:
build: ./comment
image: ${USERNAME}/comment:${VERSION3}
networks:
- reddit
volumes:
post_db:
networks:
frontend-network:
ipam:
config:
- subnet: 10.0.1.0/24
backend-network:
ipam:
config:
- subnet: 10.0.2.0/24
Файлик стал покрасивее) Основной файл с переменными не забываем добавлять в gitignore, как пример оставляем другой файл...который внезапно в логике проверок Тревиса должен прям так и называться - .env.example)