четверг, июня 19, 2008

Deployment и Git

Сперва - что такое deployment?

Буквальный перевод - развертывание. Речь идет о том, чтобы заставить код, написанный разработчиком в своей песочнице, заставить работать в реальных условиях на "боевых серверах". И вот во что это выливается даже в случае с одним разработчиком:

Мой нынешний проект я разрабатываю/тестирую на своем домашнем сервере (dev-сервер, это называется). А недавно выложил на хостинг - это, так сказать, production-сервер (ну, на самом деле, это пока что разновидность тестирования). И тут появляются некоторые ньюансы:

  • на dev- и production- серверах нужны разные настройки кода (например, разные пароли для коннекта к БД);

  • на dev-сервере я продолжаю разработку, добавляю новые фичи и пр. Хотелось бы, чтобы новые фичи и в production-варианте появлялись;

  • на production-сервере вылезают некоторые баги, которые по разным причинам на dev не вылезали, их приходится фиксить. Хотелось бы, чтобы эти же баги были пофиксены и в dev-версии;

  • Т.к. код еще не доведен "до кондиции", при переносе выясняется, что кое-где он написан неуниверсально (зависит от специфики того места, где работает). Для работы production-сервере его приходится после выкладывания обобщать. Конечно, нужно, чтобы и в dev-версии он был не менее общим;

  • Некоторые изменения, сделанные в коде production-версии, всё-таки специфичны именно для данного конкретного хостинга. Они в версии на dev-сервере мне совсем не нужны;

  • Кроме всего этого, я хочу держать актуальную версию кода в публичном месте, чтобы любой желающий мог его скачать.

Несложно догадаться, что при попытке выполнять все эти требования "вручную" - очень быстро запутаешься в трех соснах (то бишь, версиях). Для упрощения deployment-а существует довольно много всяких разных решений. Одно из возможных - использовать Git.

Предположим, код на dev-сервере уже под контролем git. Тогда для разворачивания проекта делаем:

user@production.server:/project$ git init
user@dev.server:~/project$ git push ssh://production.server/project master

Собственно, теперь на production.server в директории /project/ имеем копию кода с dev-сервера. Далее:

user@production.server:/project$ git branch production
user@production.server:/project$ git checkout production

Это мы создали новую ветвь репозитория и переключились в нее. Теперь изменяем настройки, тестируем, фиксим баги… не забываем после каждого логически завершенного изменения делать "git commit -a" - фиксировать изменения в репозитории.

При внесении изменений в код на dev-сервере их, конечно, тоже фиксируем. Когда захотим обновить версию на production, делаем:

user@dev.server:~/project$ git push
user@production.server:/project$ git merge master

— вливаем изменения в ветви master в production. Если нужно применить только последнее изменение в master (последний коммит), вместо git merge делаем git cherry-pick.

Чтобы применять нужные изменения, сделанные на production, к dev-версии, делаем:

user@dev.server:~/project$ git remote add prod ssh://production.server/project

— создаем ссылку на удаленный репозиторий,

user@dev.server:~/project$ git fetch prod/production

— получаем код из ветви production,

user@dev.server:~/project$ git cherry-pick prod/production

— это если нужно применить последнее изменение в production, или

user@dev.server:~/project$ git cherry-pick идентификатор-коммита

— если нужно применить произвольный коммит.

Системы контроля версий. Git.

Решил всё-таки написать некое введение в тему, чтобы бы было куда давать ссылки ;) Примечание: информация расчитана на не очень опытных разработчиков. Профессионалам, думаю, все освещенные здесь вопросы покажутся тривиальными. Товарищам, знакомым с контролем версий, но не знакомым с Git, предлагаю первые разделы пропустить.

Что такое управление версиями и зачем оно?

Итак, предположим, вы разрабатываете программу. Для простоты сначала предположим, что вы разрабатываете ее в одиночку и для собственного удовольствия. Как это выглядит?

Создаем директорию MyApplication, в ней - собственно файлы программы (скажем, *.c). Далее начинается итерационный процесс: написали некоторое количество кода → скомпилировали → запустили → обнаружили ошибки или недоработки → отредактировали исходники → … Всё просто, в целом. Но через некоторое время начинаются нетривиальные вещи. Например:

  • Последнее изменение сделало программу хуже, чем она была до того. Хочется вернуть всё "как было". Хорошо, если не успели закрыть редактор - делаем Undo. А если закрыли?

  • Хотим приделать к программе новую функциональность, но сразу ясно, что, во-первых, сразу после написания она будет глючить (т.к. еще не отлажена), а во-вторых, в сложной программе добавление новой функциональности может добавить ошибок в ранее работавшую часть программы. Хочется, в случае чего, откатиться к заведомо работающему варианту и начать всё сначала.

  • Если программа предназначена не только для вас лично, то вам придется делать"релизы", т.е. выпускать заведомо работающие версии программы. Для подготовки релиза вам придется фиксить некоторое количество багов. Это довольно скучный процесс, хочется разбавлять его добавлением новых возможностей в программу (это, как правило, интереснее). Но нельзя: добавление новых фич обязательно добавит багов, и вы никогда не доедете до релиза.

Первое, что приходит в голову для решения всех этих проблем - резервные копии. Скажем, каждую работающую версию программы упаковывать в MyApplication_v0.001.tar.gz, в случае чего - откатываться. Для подготовки к релизу - сделать копию директории MyApplication, в которой только фиксить баги, а в основной - добавлять функционал. При этом подходе через некоторое время разработки у вас появится куча *.tar.gz и не меньше копий рабочей директории. Очень быстро вы начнете путаться, где что.

Если вы разрабатываете программу не в одиночку, а, скажем, с группой товарищей, появляются новые неочевидности:

  • Вы добавили в программу (в свою копию) возможность A, а ваш товарищ (в свою) - возможность B. Хотелось бы, чтобы у обоих была версия с обоими возможностями.

  • Вы добавили в программу некоторую функцию, ваш товарищ добавил ее же, но реализовал по-другому. Какую версию выпускать и поддерживать дальше?

  • Ваш товарищ в какой-то момент разработки понял, что программу можно улучшить и упростить, если изменить ее архитектуру, что-то обобщить, что-то вынести в отдельный модуль и т.п., и начал переделку (это называется умным словом рефакторинг). Вы же в это время в старой версии программы находите баги и фиксите их. Т.к. в версии вашего товарища в основном тот же код, эти баги наверняка есть и там - хотелось бы их сразу пофиксить.

Даже в случае двух разработчиков, при "ручном" согласовании изменений после определенного объема программы эти неочевидности превращаются в окончательную неразбериху. При разработке сколько-нибудь большой программы в конторе "ручное" согласование изменений видится почти невозможным.

Все эти проблемы призваны решить специальные программы - системы контроля версий (version control systems, vcs).

Общие свойства VCS

Если вы работаете с vcs, то у вас есть место, где хранится вся история изменений в программе - это место называется репозиторий (repository). Для внесения изменений в программу вам нужно получить копию текущей разрабатываемой версии - она называется вашей рабочей копией (working copy). Процесс получения рабочей копии традиционно называется словом "чекаут" (checkout). В случае коллективной разработки, вам придется поддерживать актуальность вашей рабочей копии (проверять, что там остальные наработали) - это обычно называется "апдейт" (update). Чтобы ваши изменения попали в репозиторий, вам нужно сделать коммит (commit) (еще это называется словом checkin). Часто словом commit называют собственно сам набор изменений, зафиксированный этой операцией (более точный термин - changeset). Каждое зафиксированное изменение называют ревизией. Вы можете в любой момент откатить свою рабочую копию к любой предыдущей ревизии - это называется revert.

Для того, чтобы отслеживать такие вещи, как релизы, или варианты рефакторинга программы, или тестировать версии с новыми функциями - в репозитории создаются ветви разработки (branches). Для согласования хода разработки в разных ветках (она может вестись, и обычно ведется, параллельно) время от времени делают слияние веток (merge) - когда к одной ветке применяются те же изменения, что были проделаны в другой. Если какое-то место какого-то файла было по-разному изменено в разных ветках, при слиянии возникает конфликт (merge conflict) - его приходится решать вручную, выбирая один из вариантов или как-то логично их объединяя.

Так как ревизий в репозитории обычно довольно много, возникает необходимость некоторые из них как-то помечать (например, выпущенные релизы, или просто состояния программы, в которых она заведомо работала). Такие пометки называют тегами (tags). Таким образом, теги - это поименованные ревизии в репозитории.

Автоматизация всех перечисленных операций - дело VCS.

Централизованные vs распределенные VCS

Всё множество VCS можно разделить на два класса - централизованные и распределенные.

В случае с централизованной VCS репозиторий хранится на одном сервере, и все разработчики работают с ним. Очевидное преимущество: простое управление выпуском релизов и вообще ходом развития программы, раз весь код в одном месте. Очевидный недостаток: если с сервером что-то случится, работа всех разработчиков пропадет (даже в случае регулярных бэкапов - пропадет работа всех разработчиков, скажем, за последнюю неделю). Известные примеры централизованных vcs - CVS, Subversion, Perforce.

В случае с распределенной VCS, каждый разработчик имеет полную копию репозитория и работает с ним, время от времени синхронизируясь с репозиториями остальных. Таким образом, для организации целенаправленной разработки потребуются какие-то не технические, а организационно-административные средства. Зато получаем множество преимуществ:

  • Раз у каждого есть полная копия репозитория, работа всех разработчиков пропасть не может вообще.

  • Часто выполняемые операции - прежде всего, commit - происходят почти мгновенно, т.к. не требуют соединения по сети.

  • Каждый разработчик может создавать в своем репозитории ветки для каких-то экспериментов, всем остальным даже не нужно знать об этом.

  • Т.к. в распределенных VCS предполагается регулярная синхронизация репозиториев, в них гораздо более эффективно реализована операция слияния веток (здесь это одна из базовых операций, в отличие от централизованных VCS, где это делается нечасто).

  • Каждый разработчик может взять ("утянуть") у другого один или несколько коммитов, применив их к своему коду.

Git

Git - это одна из распределенных VCS (distributed version control system, dvcs). Другими примерами dvcs являются Mercurial, Bazaar, Monotone.

Git выделяется на их фоне главным образом из-за того, что он применяется для разработки ядра Linux (http://www.kernel.org). Собственно, именно для этого Git и разрабатывался. Ядро Linux - весьма немаленький проект, как по объему исходного кода, так и по числу участников, поэтому при его разработке большое внимание было уделено производительности и легкости слияния веток.

Далее я привожу часто используемые команды git. Я опускаю кучу тонкостей и многие возможности Git. Всё это можно посмотреть в документации (git help имя-команды, например git help commit). Здесь - скорее отправная точка, свод основных возможностей Git.

Для начала работы с Git вам понадобится репозиторий. Его можно либо создать с нуля, либо клонировать какой-нибудь существующий репозиторий.

$ cd project
$ git init

— создаем пустой репозиторий.

$ git clone git://remote.server/repo.git

— клонируем существующий репозиторий. Кроме собственного протокола, git поддерживает http://, ssh:// и пр.

При создании репозитория в его корневой директории появляется скрытая директория .git - она содержит собственно репозиторий, т.е. историю версий итп. Все остальное содержимое директории проекта - это ваша рабочая копия.

Если просто положить какие-нибудь файлы в директорию проекта, они сами собой не попадут под контроль Git. Чтобы Git учитывал изменения в них, их нужно добавить в репозиторий:

$ git add имя-файла-или-директории

Некоторые файлы не принято держать под контролем версий. Прежде всего, это перегенерируемые файлы, временные файлы, файлы журналов итп. Чтобы Git их вообще не замечал, нужно в корневой директории проекта создать файл .gitignore, и в него вписать список игнорируемых файлов, по одному на строку. Можно использовать маски - например, *.pyc. Сам файл .gitignore нужно добавить в репозиторий (git add .gitignore).

Чтобы Git учел изменения в файле, его нужно заново добавить (git add).

Коммит (фиксация изменений в репозитории) делается командой

$ git commit

При этом вам будет предложено ввести "сообщение коммита" (commit message) - пояснение, что вы изменили и зачем.

Чтобы предварительно учесть все изменения в файлах, нужно добавить ключ -a:

$ git commit -a

Если сразу после коммита вы обнаружили, что что-то забыли включить в него, можно поправить:

$ git commit --amend      # Добавить последние изменения в последний коммит, "заодно".

Особенность Git состоит в том, что ревизии (коммиты) обозначаются не номерами, как, например, в svn или cvs, а 40-значными идентификаторами типа a21b96…. Идентификаторы можно сокращать до первых нескольких знаков, лишь бы они однозначно определяли коммит. Кроме того, если нужный вам коммит помечен тегом, то можно использовать имя тега вместо идентификатора коммита. На последний коммит в текущей ветке можно также ссылаться по имени HEAD, на предпоследний - HEAD^, пред-предпоследний - HEAD^^ итп. Существуют и другие способы обозначить нужный коммит, подробности см. в документации.

Увидеть, какие файлы были изменены, добавлены итп после последнего коммита можно командой

$ git status

Сами изменения, сделанные с момента последнего коммита, можно посмотреть командой

$ git diff

У этой команды есть варианты: git diff <идентификатор> - посмотреть изменения с указанного коммита по текущее состояние, git diff <идентификатор1> <идентификатор2> - различия между двумя ревизиями.

Посмотреть журнал последних коммитов - git log.

Если вы создавали свой репозиторий как клон другого, то загрузить изменения исходного репозитория к себе можно командой

$ git pull

Если вам разрешено вносить изменения в исходный репозиторий (это называется push access, предполагает договоренность с владельцем репозитория, и, в зависимости от протокола доступа (ssh или еще что-то), какие-то пароли итп), это делается командой

$ git push

При этом ваши изменения вливаются в репозиторий, который вы клонировали.

Пометить только что созданный коммит можно командой

$ git tag имя-тега

Любой другой коммит помечается так:

$ git tag имя-тега идентификатор-коммита

Посмотреть список веток в репозитории -

$ git branch

(изначально создается одна ветка под названием master).

Создать новую ветку -

$ git branch имя-ветки

или

$ git branch имя-ветки идентификатор-коммита-начинающего-ветку

Переключиться на вновь созданную или любую другую ветку (в рабочей копии окажутся файлы той ветки, и последующие коммиты будут заноситься в нее) -

$ git checkout имя-ветки

С тем же успехом этой командой можно переключиться на любой коммит - git checkout идентификатор-коммита.

Влить изменения из другой ветки в текущую -

$ git merge имя-ветки-с-которой-сливаем

Можно взять из другой ветки один конкретный коммит (например, багофикс) -

$ git cherry-pick идентификатор-коммита

воскресенье, июня 15, 2008

Всякое разное: Drupal, Git, Django...

Давно я что-то не писал постов… За это время в голове много чего накопилось, теперь попытаюсь изложить.

Drupal

Вот, за прошедшее время успел более-менее поглубже изучить эту штуку. Кто не слышал - это CMS, а точнее, CMF (content management framework) такой. Я когда-то давно на него смотрел и даже сайты на нем делал, но по принципу "поставил движок, настроил - сдал". Недавно вот чуть посложнее сайты делать пришлось, даже модули к нему писал. Вобщем, drupal хоть и на PHP, но мне понравился ;) Чем именно:

  • посмотрел код - на редкость для php-приложений чистый и понятный;

  • система прав на основе ролей;

  • куча модулей, под каждую задачу, прямо unix-way;

  • эти модули на удивление хорошо дружат меджу собой, конфликтов почти нет;

  • еще понравилась идея связей между модулями за счет хуков (hooks) - для меня новая. Хук - это просто функция, вызов которой можно перехватить из другого модуля и сделать в дополнение что-то своё. Каждый модуль может создавать хуки и перехватывать существующие. В результате получается, что архитектура приложения (порядок связей между модулями) может меняться при изменении набора модулей.

  • удивительно вменяемое русскоязычное сообщество на drupal.ru.

Конечно, есть у него и проблемы - например, с производительностью не всегда всё отлично, в основном из-за того, что большое количество модулей порождают большое количество запросов к БД.

Git

До недавнего времени я весь свой программный код хранил в svn. Впрочем, это в основном были совсем небольшие программки. А тут появился на горизонте некий более крупный проект, который предполагалось разрабатывать командой (по крайней мере - не в одиночку). И именно в этот момент появился пост Ивана Сагалаева - жалоба на проблемы с merge в subversion. Мне почему-то сразу расхотелось использовать svn :) Стал смотреть на альтернативы, благо их довольно много. Git приглянулся во-первых своей распределенностью со всеми вытекающими, во-вторых - это мейнстрим (среди dvcs), что ни говори ;). Вобщем, стал изучать. Больше всего понравилось:

  • репозиторий создается фактически одной командой (git init), второй командой в него добавляются файлы (git add .), ну и третьей делается первый коммит (git commit). Получается быстрее, чем с subversion, так что имеет смысл использовать его даже для программы из пары файликов или конфига.

  • собственно факт распределенности, git получается удобнее использовать, когда команда разбросана по городу и не у всех не всегда даже интернет есть.

  • графическая морда, gitk, хорошо показывает ветви, коммиты и всё остальное.

Вобщем, перевел я весь свой код на git, благо тулза для импорта из svn в git есть (git-svn, так и называется).

Django

Эту вещь я когда-то уже тоже изучал, и даже что-то писал, но уже даже не помню, что писал :) Блог, кажется, по традиции :) Сейчас вот изучаю заново (в придачу, с предыдущего подхода многое в django успело поменяться). Кто еще про django не слышал (а тут такие есть? :D) - это фреймворк (т.е., по большому счету, набор библиотек + определенный способ их использования) для создания веб-приложений на python.

Сейчас активно пишу на django что-то типа groupware, назвал пока незамысловато - Projects (надо будет еще придумать подходящее название). Описание тут: http://iportnov.ru/projects/projects.

Еще до этой штуки стал писать библиотечку, которую пока назвал HMS (hooks modules system). Это, собственно, эксперимент: питоновский пакет, с помощью которого в любое питон-приложение можно добавить систему модулей, работающую "примерно как в друпале" (см. выше, про хуки). По-моему, оно довольно красиво смотрится в связке с Django. Сейчас у меня эта HMS входит в состав Projects, хотя пока и мало используется там (я пока что в ядре не все функции написал, не до модулей пока).

Еще в комплекте с Projects у меня есть pygit - веб-интерфейс к Git, написанный на django. При желании его можно почти безболезненно выдернуть из Projects и использовать в других проектах. Для связи с Git использует пакет GitPython. Вот только в этом gitpython есть пока баги, и тормозит он в некоторых местах… Ну, баги, надеюсь, скоро починят, а тормоза можно и стороной обойти.