Накануне запуска курса «DevOps-практики и инструменты» мы провели очередной открытый урок. Вебинар получился весьма содержательным. По сути, это была полуторачасовая практика в режиме нон-стоп:
- рассмотрели 4 основных инструмента современного DevOps-инженера, каждый из которых реализует базовые практики: инфраструктура как код, CI/CD, обратная связь;
- научились не ломать историю в Git и хорошо работать в команде;
- обсудили, чем Ansible отличается от других систем, и почему именно его мы изучаем на курсе;
- рассмотрели Docker и рассказали, почему контейнеры и микросервисы чаще побеждают монолитные архитектуры.
Рабочая среда:
- Ubuntu 18.04;
- Python 3;
- весь необходимый софт устанавливали в процессе вебинара.
Преподаватель — Лев Николаев, DevOps-инженер и тренер в компании «Экспресс 42». Занятие прошло в режиме «Демо».
Начнём с того, что запустим виртуалку, т. к. всю работу будем делать там. И сразу сделаем директорию с названием «Hello», где будем разрабатывать наше приложение:
Потом создадим файл под названием app.py, куда вставим следующий код:
#!/usr/bin/env python3
import datetime
def do_magic():
now = datetime.datetime.now()
return "Hello! {0}".format(now)
if __name__ == "__main__":
print(do_magic())
Как видите, в коде нет ничего сложного. Но если мы файл включим как модуль из другого приложения, то он ничего не будет делать, а будет делать лишь в том случае, если мы его запустим напрямую как приложение, за что отвечает следующая конструкция:
И, соответственно, в самой функции do_magic()
происходит очень простая вещь: мы получаем в переменную now
значение времени сейчас и выводим его на экран с текстом «Hello!»
.
Стильно, модно, молодёжно, просто — это так называемое Stateless-приложение, которому в принципе для работы ничего не нужно, например, базы данных. При этом наше приложение, состоящее из 10 магических строк кода, готово при получении запроса его обслужить.
Следующая важная особенность — мы хотим, чтобы наше приложение работало через web. Но давайте представим, что наш код очень сложный, а мы хотим иметь возможность его перед выкладкой как-то проверять. Для этого мы помечаем файл app.py как исполняемый и сразу проверяем в консоли, что всё работает:
Также нам понадобится инструмент, который позволит контролировать развитие приложения, к примеру, изменения в версиях и т. д. Тут пригодится всем известная система контроля версий Git, которая устанавливается простой командой:
sudo apt install git
Разумеется, потребуется создать в нашей директории Git-репозиторий, что мы и делаем, а потом выполняем ls -la
:
Отработав команду git status
, мы увидим, что коммитов пока нет, а файл, который содержит наш код, пока для гита совершенно чужероден, потому что находится в категории Untracked
:
Конечно, это надо бы поправить, но перед этим сделаем другую важную вещь — объясним гиту, кто мы такие. Почему это важно? Дело в том, что когда вы сохраняете какую-нибудь версию кода, вы должны написать, кто сделал коммит, как его зовут, и какой у него адрес электронной почты. Без этих настроек гит попросту не даст нам сделать коммит.
Нам помогут две простые команды:
Теперь добавим наш файлик, сообщив, что это первая версия приложения. И после успешного срабатывания коммита сразу же посмотрим историю командой git log:
Если хотим увидеть более подробную историю изменений, к команде git log добавляем параметр –p
:
Как видим, теперь к каждому коммиту будет прикрепляться diff тех изменений, которые происходили, т. е. мы будем видеть какие строчки добавили, а какие убрали. Это очень удобно, если вы работаете с кодом.
Теперь представьте, что от нас требуют, чтобы приложение открывалось и работало в браузере. Это можно сделать мегастародревним методом.
Для начала установим Apache:
sudo apt install apache2
Проверив браузер, увидим, что Apache установлен:
Apache ожидает, что он будет сбрасывать файлы, которые у него будут, в определённую директорию. Её можно увидеть, но там пока ничего нет:
Давайте теперь пойдём в директорию /var/www/html
и сделаем очень простую вещь: перенесём директорию старого html в html2. А дальше сделаем ещё одну хитрую вещь: директорию html направим на домашнюю директорию, где у нас лежит приложение:
Что произошло в итоге? Мы создали ярлык, но ярлык этот — html и, как говорится, отряд не заметит потери бойца. Теперь при наших обращениях к веб-серверу Apache будет бежать в нашу директорию, где и лежит приложение. Но просто так он приложение запускать не будет.
А значит, надо продолжить настройку. Во-первых, следует включить отвечающий за выполнение внешних скриптов модуль Apache:
Но пока запускать не будем, ведь ещё нужно добавить возможность переопределять настройки Apache путём создания файлов в нашей директории с проектом. Для этого давайте поправим сайт Apache по умолчанию:
И добавим сюда очень простую конструкцию: скажем, что в директории /var/www/html
можно переопределять всё, что угодно, добавив всего 3 строчки кода:
Теперь можно перезапустить Apache, пойти в свою директорию с проектом и сделать специальный файл .htaccess
, который позволит переопределять настройки на лету:
Вот и настройки:
Расшифровываем по строчкам:
- Если видишь файл с расширением
.py
, то этоcgi-script
, и его надо исполнить. - Разрешаем выполнение скриптов в нужной директории.
- Если не сказали иное, в качестве индекса нужно выбирать файл
app.py
.
Как видите, настройки несложные. Но если мы попробуем обновить браузер, то увидим Internal Server Error. На самом деле, причина заключается в том, что наш скрипт выводит время и сообщение «Hello!», но это не совсем то, что ожидает от него Apache. Проблема решается путём добавления одной простой строчки в код:
#!/usr/bin/env python3
import datetime
def do_magic():
now = datetime.datetime.now()
return "Hello! {0}".format(now)
if __name__ == "__main__":
print("Content-type: text/htmlnn")
print(do_magic())
Этой строчкой (print("Content-type: text/htmlnn")
) мы говорим, что перед тем, как выводить магию, нужно написать Content-type: text/htmlnn
, чтобы Apache лучше понимал.
Вуаля! Путь от простейшего обычного приложения до простейшего веб-приложения мы прошли за минут 20:
Теперь фиксируем эту версию, добавляя в коммит только app.py
:
Посмотрев лог, увидим, что у нас уже две версии, причём вторая версия модифицирована под исполнение CGI.
Всё здорово, но хочется универсальности. Чтобы, когда мы запускаем приложение командой ./app.py
, у нас не вылезал текст Content-type: text/html
:
Для этого редактируем наш код, модифицируя его:
#!/usr/bin/env python3
import datetime
import os
def do_magic():
now = datetime.datetime.now()
return "Hello! {0}".format(now)
if __name__ == "__main__":
if 'REQUEST_URI' in os.environ:
print("Content-type: text/htmlnn")
print(do_magic())
Теперь, если вызов происходит через Apache, строчка print("Content-type: text/htmlnn")
добавляется, а если вызов осуществляется просто так, то она убирается (не показывается).
Далее проверяем статус и добавляем в проект файл .htaccess
, поскольку он часть нашего кода. И делаем коммит:
Всё вышло очень даже неплохо, поэтому пришло время пометить то, что получилось, как некую версию, добавив тег:
Что даёт тег? Например, возможность возвращаться (откатываться) к нужным версиям кода.
Краткий вывод из этого демо:
- GIT помогает нам двигаться по версиям кода и понимать, что происходит;
- некоторые коммиты мы можем помечать особо (теги).
На самом деле, всё только начинается… Во-первых, вам скажут, что CGI не круто, ведь вы на каждый запрос создаёте свой отдельный процесс. Во-вторых, для Python стильно, модно и молодёжно использовать WSGI (whiskey). В-третьих, в качестве софтовой реализации мы можем использовать uWSGI. А значит, двигаемся дальше.
Итак, мы хотим продолжать работать с кодом и вносить в него изменения, но не хотим, чтобы наши коллеги это видели, и чтобы им это как-то мешало. Поэтому используем фичу гита под названием branch. Она позволяет создать ответвление (копию репозитория со всем его кодом), куда дальше будем складывать свои коммиты. А когда мы посчитаем, что разработка закончена, можно будет влить изменения в основной репозиторий.
Для создания копии репозитория выполняем простую команду:
Для совместимости со стандартом uWSGI вносим изменения в код:
#!/usr/bin/env python3
import datetime
import os
def do_magic():
now = datetime.datetime.now()
return "Hello! {0}".format(now)
def application(env, start_response):
start_response('200 OK', [('Content-Type','text/html')])
return [do_magic().encode()]
if __name__ == "__main__":
if 'REQUEST_URI' in os.environ:
print("Content-type: text/htmlnn")
print(do_magic())
Теперь нужно установить то, что нужно для работы этой версии, ведь uWSGI — это особый сервер:
Чтобы запустить наш файл, выполняем следующую замечательную конструкцию:
В ней мы просим:
- запустить демон uWSGI;
- подгрузить плагин для Python 3;
- запустить веб-сервер на порту :9090;
- использовать файл app.py в качестве стартового.
Ура, всё работает:
Дальше мы понимаем, что нам достаточно утомительно запоминать те настройки, с которыми нужно запускать сервер uWSGI. Поэтому воспользуемся возможностью uWSGI тащить данные из файла. Создадим файл dev.ini
, положив в него все необходимые параметры:
[uwsgi]
plugin=python3
http-socket=:9090
wsgi-file=app.py
И теперь всё запускается с помощью простой команды uwsgi dev.ini
.
Посмотрев статус рабочей директории, добавляем файлы app.py
и dev.ini
, делаем коммит, после чего можем выполнить слияние, вытащив свои изменения в основную ветку.
Мы увидим, что наши изменения отлично влились поверх того, что было.
Что же мы увидели в этом демо:
- git позволяет нам ветвиться — создавать полные копии для редактирования;
- а потом их можно вливать в основную ветку master;
- пока мы редактируем в ветке, мы никому не мешаем.
Пока мы работаем исключительно локально, однако это не значит, что другие не могут делать коммиты тут же.
Итак, у нас сейчас используется встроенный сервер uWSGI, но это не очень круто, ведь он не очень производительный. А вообще, хорошо бы использовать nginx, т. к. это модно. Кроме того, шеф на переговорах, поэтому в ближайшее время проект придётся выкатывать, а в воздухе пахнет скорым деплоем.
Начнём с того, что выключим Apache2 и установим nginx
.
А теперь создадим простенький конфигурационный файл, который позволит nginx принимать входящие соединения и пробрасывать их на uWSGI, то есть получим более-менее современный веб-стек. Но прежде удалим файлы конфигурации nginx по умолчанию, т. к нам нужно положить туда свои. Также создаём файл конфигурации нашего сервера и перезапускаем nginx
:
Вот содержимое нашего конфигурационного файла:
server {
listen 80;
root /var/www/html;
location / {
include /etc/nginx/uwsgi_params;
uwsgi_pass 127.0.0.1:9000;
uwsgi_param Host $host;
uwsgi_param X-Real-IP $remote_addr;
uwsgi_param X-Forwarded-For $proxy_add_x_forwarded_for;
uwsgi_param X-Forwarded-Proto $http_x_forwarded_proto;
}
}
На самом деле конфигурация очень простая: мы просим сервер слушать на 80-м порту, в качестве корневой директории использовать /var/www/html
, а при обращении корневой директории пробрасывать трафик на адрес 127.0.0.1:9000
. Причём не просто пробрасывать, а делать это в формате uwsgi
, где его будет ждать демон uwsgi
и отдавать ему результат своей работы, в результате чего пользователи смогут видеть наше замечательное приложение. И именно таким образом с nginx
и uwsgi
мы будем работать в production
.
Теперь можно переходить в нашу директорию и копировать настройки, созданные на этапе разработки, в настройки прода.
И редактируем настройки, меняя одну строчку:
[uwsgi]
plugin=python3
socket=127.0.0.1:9000
wsgi-file=app.py
То есть вместо HTTP-сервера мы просим открыть сокет, готовый к общению по протоколу uwsgi
, куда может обращаться nginx
.
Далее идём в нашу директорию и запускаем uwsgi
с настройками прода:
После чего убеждаемся, что всё работает и готово к деплою:
Каковы итоги Demo 4:
- мы постепенно начали готовиться к проду;
- обычно стек технологий состоит из множества компонентов, где каждый выполняет свою работу;
nginx
обрабатывает много запросов и делает это хорошо;uWSGI
умеет вести себя, как надо в нужное количество ресурсов;- у нас появился первый артефакт — конфиг
nginx
(на самом деле, не первый, но первый, который лежит не у нас).
Не успели мы допить очередную чашку кофе, как прибегает шеф и говорит, что пора бы и деплой делать. Это значит, что нам предстоит писать не просто код, а инфраструктурный код, то есть, тот код, который умеет выкатывать наше приложение.
Не руками же это делать, ведь у клиента 5 тысяч серверов, и они находятся в разных дата-центрах и разбросаны по миру, и нам нужно туда задеплоить наше приложение. И мы понимаем, что нам нужно как-то переходить к хранению инфраструктурного кода. В данном случае мы принимаем инженерное решение хранить инфраструктурный код в одной директории с нашим кодом приложения. Это, кстати, одна из практик девопса. Когда мы с вами говорим про деплой, то подразумеваем, что деплой должен быть автоматизирован, автоматизация означает написание инфраструктурного кода, а мы можем хранить инфраструктурный код как с приложением, так и отдельно. Так как приложение простое, мы принимаем решение хранить всё вместе.
Что делаем дальше? Идём в нашу директорию и создаём одним движением 5 директорий, причём они разные:
Зачем нам это? Дело в том, что наш код может запускаться при помощи Apache, при помощи nginx, uWSGI, да и systemd понадобится, поэтому надо красиво и качественно это всё вместе разложить. Что мы и делаем:
Как видим, репозиторий стал красив: приложение app.py
осталось на месте, всё остальное уехало в директорию deploy
. Теперь можем добавить в отслеживание директорию deploy и сделать коммит сразу со всеми изменёнными файлами, сказав, что мы приготовились к деплою:
Что ж, настало время поделиться кодом с внешним миром. А значит, надо подготовить возможность этот код куда-то выложить. В нашем случае можно воспользоваться своим аккаунтом на гитхабе, создав публичный репозиторий с именем «Hello». Для работы с гитхабом выполняем необходимые настройки. Т. к. всё настроено с помощью SSH-ключей, никаких логинов и паролей вводить не нужно.
Забрасываем изменения на гитхаб:
Вроде бы всё клёво, только в нашем удалённом репозитории не будет хватать тегов. Почему, ведь мы же их делали? Дело в том, что теги не переносятся автоматически в удалённый репозиторий, поэтому мы тег передадим вручную:
Теперь наша версия появится в разделе релизов и будет представлять собой некую версию приложения, которую можно будет скачать в архиве:
У нас есть ещё одна задача: сделать так, чтобы наш демон uWSGI запускался автоматически, при этом мы не хотим использовать никакие внешние инструменты. Для решения этой задачи создадим юнит-файл для systemd
:
Со следующим содержимым:
[Unit]
Description=Hello app
Requires=network.target
After=network.target
[Service]
TimeoutStartSec=0
RestartSec=10
Restart=always
WorkingDirectory=/opt/hello
KillSignal=SIGQUIT
Type=notify
NotifyAccess=all
ExecStart=/usr/bin/uwsgi deploy/uwsgi/prod.ini
[Install]
WantedBy=multi-user.target
Как видите, код достаточно простой, но есть интересные моменты
.
Файл сохраняем и просим systemd
перепрочитать изменения с диска, чтобы он увидел новый сервис. Но прежде чем это сделать, надо зайти в директорию opt
, стать суперпользователем и сказать, что хотим клонировать репозиторий:
Теперь можем смело стартовать, и консоль не будет ругаться:
Проверив, убедимся, что всё работает красиво, за всем приглядывает systemd
, и всё замечательно.
Мы в принципе построили некую конфигурацию, которую хотели бы видеть в деплое. Теперь возвращаемся в домашнюю директорию, копируем наш скрипт и кладём его в папку deploy
:
И сейчас мы в принципе готовы деплоиться на любой сервер. А значит, можно делать коммит и создать новый релиз 2.0:
Что же, на этом остановим нашу текстовую трансляцию. Однако вы можете продолжить просмотр, так как день девопс-инженера ещё не закончился и впереди ещё:
- Demo 6 — используем Ansible для автоматизации деплоя;
- Demo 7 и Demo 8 — практикуем Docker и упрощаем запуск контейнера с помощью docker-compose;
- Demo 9 — добро пожаловать в Kubernetes!
И держите ссылку на GitHub проекта.
Автор: Дмитрий