29 ноября 2019

Один день из жизни DevOps

Блог компании OTUS. Онлайн-образованиеПрограммированиеDevOps



Накануне запуска курса «DevOps-практики и инструменты» мы провели очередной открытый урок. Вебинар получился весьма содержательным. По сути, это была полуторачасовая практика в режиме нон-стоп:

  • рассмотрели 4 основных инструмента современного DevOps-инженера, каждый из которых реализует базовые практики: инфраструктура как код, CI/CD, обратная связь;
  • научились не ломать историю в Git и хорошо работать в команде;
  • обсудили, чем Ansible отличается от других систем, и почему именно его мы изучаем на курсе;
  • рассмотрели Docker и рассказали, почему контейнеры и микросервисы чаще побеждают монолитные архитектуры.

Рабочая среда:

  • Ubuntu 18.04;
  • Python 3;
  • весь необходимый софт устанавливали в процессе вебинара.

Преподаватель — Лев Николаев, DevOps-инженер и тренер в компании «Экспресс 42». Занятие прошло в режиме «Демо».

Demo 1

Начнём с того, что запустим виртуалку, т. к. всю работу будем делать там. И сразу сделаем директорию с названием «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 тех изменений, которые происходили, т. е. мы будем видеть какие строчки добавили, а какие убрали. Это очень удобно, если вы работаете с кодом.

Demo 2

Теперь представьте, что от нас требуют, чтобы приложение открывалось и работало в браузере. Это можно сделать мегастародревним методом.

Для начала установим Apache:

sudo apt install apache2

Проверив браузер, увидим, что Apache установлен:



Apache ожидает, что он будет сбрасывать файлы, которые у него будут, в определённую директорию. Её можно увидеть, но там пока ничего нет:



Давайте теперь пойдём в директорию /var/www/html и сделаем очень простую вещь: перенесём директорию старого html в html2. А дальше сделаем ещё одну хитрую вещь: директорию html направим на домашнюю директорию, где у нас лежит приложение:



Что произошло в итоге? Мы создали ярлык, но ярлык этот — html и, как говорится, отряд не заметит потери бойца. Теперь при наших обращениях к веб-серверу Apache будет бежать в нашу директорию, где и лежит приложение. Но просто так он приложение запускать не будет.

А значит, надо продолжить настройку. Во-первых, следует включить отвечающий за выполнение внешних скриптов модуль Apache:



Но пока запускать не будем, ведь ещё нужно добавить возможность переопределять настройки Apache путём создания файлов в нашей директории с проектом. Для этого давайте поправим сайт Apache по умолчанию:



И добавим сюда очень простую конструкцию: скажем, что в директории /var/www/html можно переопределять всё, что угодно, добавив всего 3 строчки кода:



Теперь можно перезапустить Apache, пойти в свою директорию с проектом и сделать специальный файл .htaccess, который позволит переопределять настройки на лету:



Вот и настройки:



Расшифровываем по строчкам:

  1. Если видишь файл с расширением .py, то это cgi-script, и его надо исполнить.
  2. Разрешаем выполнение скриптов в нужной директории.
  3. Если не сказали иное, в качестве индекса нужно выбирать файл 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/html\n\n")
  print(do_magic())

Этой строчкой (print("Content-type: text/html\n\n")) мы говорим, что перед тем, как выводить магию, нужно написать Content-type: text/html\n\n, чтобы 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/html\n\n")
  print(do_magic())

Теперь, если вызов происходит через Apache, строчка print("Content-type: text/html\n\n") добавляется, а если вызов осуществляется просто так, то она убирается (не показывается).

Далее проверяем статус и добавляем в проект файл .htaccess, поскольку он часть нашего кода. И делаем коммит:



Всё вышло очень даже неплохо, поэтому пришло время пометить то, что получилось, как некую версию, добавив тег:



Что даёт тег? Например, возможность возвращаться (откатываться) к нужным версиям кода.

Краткий вывод из этого демо:

  • GIT помогает нам двигаться по версиям кода и понимать, что происходит;
  • некоторые коммиты мы можем помечать особо (теги).

На самом деле, всё только начинается… Во-первых, вам скажут, что CGI не круто, ведь вы на каждый запрос создаёте свой отдельный процесс. Во-вторых, для Python стильно, модно и молодёжно использовать WSGI (whiskey). В-третьих, в качестве софтовой реализации мы можем использовать uWSGI. А значит, двигаемся дальше.

Demo 3

Итак, мы хотим продолжать работать с кодом и вносить в него изменения, но не хотим, чтобы наши коллеги это видели, и чтобы им это как-то мешало. Поэтому используем фичу гита под названием 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/html\n\n")
  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;
  • пока мы редактируем в ветке, мы никому не мешаем.

Пока мы работаем исключительно локально, однако это не значит, что другие не могут делать коммиты тут же.

Demo 4

Итак, у нас сейчас используется встроенный сервер 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 (на самом деле, не первый, но первый, который лежит не у нас).

Demo 5

Не успели мы допить очередную чашку кофе, как прибегает шеф и говорит, что пора бы и деплой делать. Это значит, что нам предстоит писать не просто код, а инфраструктурный код, то есть, тот код, который умеет выкатывать наше приложение.

Не руками же это делать, ведь у клиента 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 проекта.
Теги:devopsdockerdeploykubernetes
Хабы: Блог компании OTUS. Онлайн-образование Программирование DevOps
+8
6,8k 84
Комментарии 1
Лучшие публикации за сутки