Один день из жизни 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 проекта.
    OTUS. Онлайн-образование
    380,54
    Цифровые навыки от ведущих экспертов
    Поделиться публикацией

    Комментарии 1

      +2
      python, apt, apache, git — это и есть devops?
      Я думал без контейнеров это просто опс.
      </irony

      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

      Самое читаемое