Python deployment in een virtuele omgeving met virtualenv


Hoe Programmeur Jan en Systeembeheerder Piet ervoor zorgen dat ze geen ruzie met elkaar krijgen.

Dit artikel gaat ervan uit dat je minimaal een basis begrip hebt van UNIX/Linux-syteembeheer, applicatie deployment, Python, Python modules, python-pip, de werking van sudo, eventueel git, recente ontwikkelingen op het gebied van Cloud Computing en dat de planeet aarde geen platte pannenkoek is.

Inleiding

Stel je de volgende veel voorkomende situatie voor: Programmeur Jan heeft de opdracht gekregen om een programma te schrijven in Python wat uiteindelijk op een aantal productie servers moet uitgerold gaan worden. Programmeur Jan gaat enthousiast aan de slag op zijn Ubuntu laptop of ontwikkelomgeving om deze Python applicatie te gaan bouwen.

Tijdens het programmeren maakt Jan gebruik van een of meerdere niet-standaard Python modules die via de import functie in zijn script worden geimporteerd. Om dit mogelijk te maken zal Jan er wel eerst voor moeten zorgen dat deze extra Python modules ook echt geinstalleerd zijn op zijn lokale ontwikkelomgeving, anders kan Jan zijn code niet lokaal testen. Tot zover niets aan de hand.

Na een tijdje heeft Jan de Programmeur zijn script klaar en heeft het uitvoerig getest op zijn locale machine. Laten we voor dit artikel aannemen dat hij het hieronder getoonde programma geschreven en getest heeft:

#!/usr/bin/env python
#
# bs4example.py
#
from bs4 import BeautifulSoup
import sys,os

print("Hello, world!")

# rest van het script #

In dit programma wordt de python module ‘Beautiful Soup versie 4′ geimporteerd die programmeur Jan keurig netjes op zijn lokale ontwikkelsysteem heeft geinstalleerd in /usr/local/lib/python3.3/dist-packages/. Daarnaast importeerd hij ook de modules sys en os, maar dat zijn standaard modules in Python.

Let er ook op dat Programmeur Jan geen absoluut pad gebruikt in het aanroepen van de Python intepreter in de eerste regel:

#!/usr/bin/env python

in plaats van:

#!/usr/bin/python

Python modules worden per definitie niet aangeroepen met een absoluut pad, maar dat wist je natuurlijk al.

Volgens Jan is de code nu klaar om op productiesystemen geinstalleerd te gaan worden. Probleem is nu dat hij de systeembeheerder van de productiesystemen Piet precies moet vertellen welke extra Python module(s) en welke versies hiervan er nog geinstalleerd moeten worden op de productiesystemen om het script nu en in de toekomst te laten werken zoals bedoeld is.

Dat is op zich nog niet zo’n probleem, ware het niet dat Piet liever niet al te veel extra afhankelijkheden installeerd die in de toekomst problemen kunnen gaan opleveren met updates van versies van die modules. Of het betreffen versies van modules die (kunnen) conflicteren met andere Python programma’s die op diezelfde machines draaien. Programmeur Jan en Systeembeheerder Piet hebben op dit moment conflicterende belangen. Of het hogere management wil het programma in de toekomst wellicht gaan uitrollen op productie machines bij een Cloud provider waar zowel Jan als Piet geen permissies hebben om extra systemwide Python modules te gaan installeren.

Oplossing

Omdat programmeur Jan een slimme programmeur is, voorziet hij deze eventuele problemen. Hij besluit om een andere aanpak te kiezen, namelijk door het Python programma en de modules die daarvan afhankelijk zijn in een geisoleerde omgeving te gaan bouwen met virtualenv.

Inrichten ontwikkelomgeving

Jan heeft een directory op zijn lokale ontwikkelomgeving waar hij al zijn Python development in doet. Hij heeft ook al vantevoren setuptools en pip (zie https://pypi.python.org/pypi/pip) geinstalleerd op zijn locale Linux laptop via sudo:

jan@laptop:~$ sudo yum install python-pip (of debian/ubuntu: sudo apt-get install python-pip)
jan@laptop:~$ mkdir -p Development
jan@laptop:~$ cd Development

Hierin maakt hij een directory aan voor zijn nieuwe project:

jan@laptop:~/Development$ mkdir -p bs4example
jan@laptop:~/Development$ cd bs4example

Vervolgens installeert hij virtualenv via pip (met sudo):

jan@laptop:~/Development/bs4example$ sudo pip install virtualenv

Daarna maakt hij een virtuele omgeving binnen zijn ontwikkel directory met de naam ‘venv’ (zonder sudo):

jan@laptop:~/Development/bs4example$ /usr/local/bin/virtualenv venv
Using base prefix '/usr'
New python executable in venv/bin/python3.3
Also creating executable in venv/bin/python
Installing setuptools, pip...done.

Dit zorgt ervoor dat er in eerste instantie een copie van de Python executable, de standaard Python modules, alsmede setuptools en pip worden neergezet in de virtuele omgeving ~/Development/nulinks/venv/ directory van user jan:

jan@laptop:~/Development/bs4example$ ls ~/Development/bs4example/venv/
bin/ lib/
jan@laptop:~/Development/bs4example$ ls ~/Development/bs4example/venv/bin
activate      activate.fish     easy_install      pip   pip3.3  python3
activate.csh  activate_this.py  easy_install-3.3  pip3  python  python3.3
jan@laptop:~/Development/bs4example$ ls ~/Development/bs4example/venv/lib
python3.3/

Jan wil nu gaan beginnen met programmeren en testen. Daarvoor moet hij als eerste de virtuele omgeving activeren:

jan@laptop:~/Development/bs4example$ source venv/bin/activate

Het sourcen van bin/activate zorgt ervoor dat je shell environment variabelen worden aangepast. Het de-activeren van de virtuele omgeving gebeurd overigens met (je raad het nooit..) het commando deactivate. Vergelijk nu de volgende twee situaties.

Voor het sourcen van bin/activate:

jan@laptop:~/Development/bs4example$ which python
/usr/bin/python
jan@laptop:~/Development/bs4example$ which pip
/usr/local/bin/pip

Na het sourcen van bin/activate:

jan@laptop:~/Development/bs4example$ source venv/bin/activate
(venv)jan@laptop:~/Development/bs4example$ which python
/home/jan/Development/bs4example/venv/bin/python
(venv)jan@laptop:~/Development/bs4example$ which pip
/home/jan/Development/bs4example/venv/bin/pip

Merk op dat de shell prompt van user jan veranderd om aan te geven dat je nu in de virtuele omgeving zit, zoals in het voorbeeld hierboven waarbij er (venv) voor de prompt string wordt geprint.

Programmeur Jan gaat nu zijn script bs4example.py testen in zijn virtuele omgeving:

(venv)jan@laptop:~/Development/bs4example$ ls
bs4example.py  venv/
(venv)jan@laptop:~/Development/bs4example$ chmod +x bs4example.py
(venv)jan@laptop:~/Development/bs4example$ ./bs4example.py
Traceback (most recent call last):
  File "./bs4example.py", line 5, in 
    from bs4 import BeautifulSoup
ImportError: No module named 'bs4'

Dit resulteert zoals verwacht in een ImportError. Ondanks het feit dat Jan op zijn lokale ontwikkelomgeving wel degelijk de Python module beautifulsoup4 system-wide heeft geinstalleerd, kan het python script binnen de virtuele omgeving de Python module niet vinden. Hetzelfde Python script zou buiten de virtuele omgeving deze module wel zonder moeite kunnen localiseren (die staat system-wide geinstalleerd in /usr/local/lib/python3.3/dist-packages/ directory).

Jan moet dus nog 1 ding doen. Hij moet deze module (als user jan) ook nog installeren in de virtuele omgeving (dus zonder gebruik te maken van sudo):

(venv)jan@laptop:~/Development/bs4example$ pip install beautifulsoup4
Downloading/unpacking beautifulsoup4
  Downloading beautifulsoup4-4.3.2.tar.gz (143kB): 143kB downloaded
  Running setup.py (path:/home/jan/Development/bs4example/venv/build/beautifulsoup4/setup.py) egg_info for package beautifulsoup4

Installing collected packages: beautifulsoup4
  Running setup.py install for beautifulsoup4
    Skipping implicit fixer: buffer
    Skipping implicit fixer: idioms
    Skipping implicit fixer: set_literal
    Skipping implicit fixer: ws_comma

Successfully installed beautifulsoup4
Cleaning up...

Nu is deze module mooi geinstalleerd binnen de virtuele omgeving:

(venv)jan@laptop:~/Development/bs4example$ ls venv/lib/python3.3/site-packages/bs4/
builder/  dammit.py  diagnose.py  element.py  __init__.py  __pycache__/  testing.py  tests/

Programmeur Jan gaat nu zijn script bs4example.py nogmaals uitvoeren in zijn virtuele omgeving:

(venv)jan@laptop:~/Development/bs4example$ ls
bs4example.py  venv/
(venv)jan@laptop:~/Development/bs4example$ ./bs4example.py
hello world!

Perfect. Nu werkt het. De bs4 module is nu gevonden in de virtuele omgeving en Jan is in principe klaar.

Deployment

Nu kan programmeur Jan zijn code gaan opleveren. Er rest hem echter nog 1 ding te doen. Jan moet duidelijk maken aan de beheerder Piet van de productie server(s) of aan het geautomatiseerde deployment systeem van een Cloud provider zoals Heroku of OpenStack dat er nog extra Python modules (vaak ook nog van een specifiek versienummer) geinstalleerd moeten worden in een virtuele omgeving op de productieserver, anders werkt de applicatie natuurlijk niet.

Deze afhankelijkheden zullen dus in een bestand gezet moeten worden. De conventie is dat dit bestand requirements.txt genoemd wordt in de root van je project directory. Dit kan Jan automatisch genereren of re-genereren door middel van dit commando:

(venv)jan@laptop:~/Development/bs4example$ pip freeze > requirements.txt

In het geval van jan ziet de uiteindelijke requirements.txt er dan zo uit, maar dat kan in andere gevallen ook een hele lange lijst worden als er in programma veel verschillende niet-standaard Python modules aangeroepen worden:

(venv)jan@laptop:~/Development/bs4example$ cat requirements.txt
beautifulsoup4==4.3.2

Dus de namen en (optioneel) de versienummers van de python module die nodig zijn voor het script. Jan gaat nu zijn script inpakken in een tar archive of een zip file, inclusief requirements.txt, maar exclusief de venv/ directory en alle onderliggende subdirectories van de venv directoryOf hij kan het geheel inchecken in een source code repository zoals git, maar dan ook weer inclusief requirements.txt, maar exclusief de venv/ directory en alle onderliggende subdirectories van de venv/ directory.

Deployment op een of meerdere productie servers

In het geval van een deployment op een of meerdere productie servers door systeembeheerder Piet, kan Piet de virtuele venv/ omgeving makkelijk re-creeren:

root@webserver1:~# cd /opt
root@webserver1:/opt# (doe een git checkout als de sources in git staan of unpack tar/zip archive hier)
root@webserver1:/opt# cd bs4example
root@webserver1:/opt/bs4example# ls
bs4example.py  requirements.txt
root@webserver1:/opt/bs4example# virtualenv venv
Using base prefix '/usr'
New python executable in venv/bin/python3.3
Also creating executable in venv/bin/python
Installing setuptools, pip...done.
root@webserver1:/opt/bs4example# source venv/bin/activate
(venv)root@webserver1:/opt/bs4example# pip install -r requirements.txt
Downloading/unpacking beautifulsoup4==4.3.2 (from -r requirements.txt (line 1))
  Downloading beautifulsoup4-4.3.2.tar.gz (143kB): 143kB downloaded
  Running setup.py (path:/opt/bs4example/venv/build/beautifulsoup4/setup.py) egg_info for package beautifulsoup4

Installing collected packages: beautifulsoup4
  Running setup.py install for beautifulsoup4
    Skipping implicit fixer: buffer
    Skipping implicit fixer: idioms
    Skipping implicit fixer: set_literal
    Skipping implicit fixer: ws_comma

Successfully installed beautifulsoup4
Cleaning up...

Let erop dat dit dus de juiste Python modules op de productie server(s) installeerd in de nieuwe virtuele omgeving. Op deze manier kun je dus op 1 enkele productie server meerder python applicaties draaien, ook als deze alle verschillende Python modules vereisen, zelfs met verschillende versie nummers.

Deployment in de Cloud

In het geval van een deployment van deze applicatie op een cloud gebaseerde infrastructuur zoals Heroku of OpenStack is het nog eenvoudiger. De deployment scripts op de cloud server zullen zelf by default een virtualenv aan gaan maken en de juiste modules uit requirements.txt gaan installeren in jouw eigen virtualenv. Daarvoor is het wel van belang dat je sources in git staan, maar als je in dit stadium als ontwikkelaar je code nog niet in een git repository had staan, verdien je het zowieso om met een 14 inch floppy disk flink om je oren geslagen te worden…

Alternatief

Voor de volledigheid is het van belang om op te merken dat er ook andere oplossingen zijn dan virtualenv voor hetzelfde probleem. Jan had een chroot omgeving kunnen maken, maar dan moet je volgens mij distutils in plaats van pip gebruiken en handmatig de setup.py van iedere Python module hacken om hem te dwingen om in de chrooted omgeving te installeren.

Of Programmeur Jan en Systeembeheerder Piet hadden er bijvoorbeeld ook samen voor kunnen kiezen om de applicatie te bouwen en te deployen via Docker, een open source project om applicaties verpakken en te deployen als een  lichtgewicht container, maar dat is dan wellicht een leuk onderwerp voor een volgend artikel. Docker heeft als voordeel dat het niet Python specifiek is en dus ook goed gebruikt zou kunnen worden voor een Perl, Ruby, of Java applicatie. De meeste Cloud providers ondersteunen ook Docker containers voor zover ik heb kunnen achterhalen.

Success!

Lees ook:

voor alle overige opties en mogelijkheden die niet in dit artikel aan de orde gekomen zijn.

Tags: Python, virtualenv, Virtual Environment, Cloud, pip

Joost Soeterbroek <joost.soeterbroek@proxy.nl> is een UNIX/Linux System Engineer werkzaam voor Proxy BV in Rotterdam (www.proxy.nl).

Leave a comment

Your email address will not be published. Required fields are marked *