Chapter 13: Recepty wdrożeniowe

Wdrożenie: Tworzenie i eksploatacja

appconfig

Moduł AppConfig

Środowisko programistyczne i produkcyjne prawie zawsze mają inną konfigurację bazy danych, różne serwery pocztowe i ewentualnie inne różnice.

W web2py stosuje się prywatny katalog do przechowywania informacji, które nie są replikowane w typowym wdrożeniu produkcyjnym (trzeba zwrócić uwagę, aby nie przekazywać tego katalogu na serwer produkcyjny).

AppConfig jest społecznościowym modułem, który umożliwia przechowywanie prostego tekstowego pliku konfiguracyjnego ze zdefiniowanymi ustawieniami, różnicującymi środowisko programistyczne i produkcyjne, takie jak połączenie z bazą danych. Domyślnie jest to plik "pythonowy", ale obsługiwany jest też format JSON.

Aplikacja "welcome" wykorzystuje teraz ten moduł w db.py do odczytywania konfiguracji z pliku umieszczonego w prywatnym katalogu aplikacji. Domyślna ścieżka do tego pliku, to private/appconfig.ini.

Plik appconfig.ini umożliwia definiowanie konfiguracji połączenia z bazą danych i konfigurację SMTP. Gdy aplikacja jest już stabilna, moduł ten można ustawić na tryb buforowania w celu zmniejszenia obciążenia.

from gluon.contrib.appconfig import AppConfig
...
myconf = AppConfig(reload=False)

Aplikacje utworzone w nowej wersji web2py mają już domyślnie zdefiniowane połączenie z bazą danych w AppConfig.

Wartości w app_config.ini są pobierane i zrzucane z wartości łańcuchowej, w ten sposb:

myconf = AppConfig()
...
a_config_value = myconf.take('example_section.example_key',cast=int)

Ponieważ zrzucanie następuje z łańcucha a niepusty łańcuch zrzuca True, to najbezpieczniejszym sposobem reprezentowania logicznego fałszu jest pusty łańcuch:

[example_section]
example_key = 

Recepty wdrożeniowe: Infrastruktura

Istnieje wiele sposobów na wdrożenie web2py w środowisku produkcyjnym. Szczegóły zależą od konfiguracji i usług dostarczanych przez host.

W tym rozdziale rozpatrzymy następujące zagadnienia:

  • wdrożenie produkcyjne (Apache, Nginx, Lighttpd, Cherokee)
  • bezpieczeństwo,
  • skalowalność przy wykorzystaniu Redis i load balancera.
  • wdrożenie na PythonAnywhere, Heroku, Amazon EC2 i platformie Google App Engine (GAE[gae] )

Apache
CGI
mod_python
mod_wsgi
mod_proxy
WSGI
Nginx
Heroku
PythonAnywhere

Platforma web2py dostarczana jest z SSL[ssl] włączonym na serwerze internetowym wsgi Rocket[rocket]. Chociaż jest to szybki serwer internetowy, to ma on ograniczone możliwości konfiguracyjne. Z tego powodu najlepiej jest wdrożyć web2py na serwerze Apache[apache] , Nginx[Nginx] Lighttpd[lighttpd] lub Cherokee[cherokee] . Są to bezpłatne, wolne serwery internetowe, które są konfigurowalne i okazały się niezawodne w środowisku produkcyjnym o dużym natężeniu ruchu. Mogą zostać skonfigurowane do bezpośredniej obsługi plików statycznych, z protokołem HTTPS i przekazywania tworzenia dynamicznej zawartości do web2py.

Jeszcze kilka lat temu, standardowym interfejsem komunikacyjnym dla komunikacji pomiędzy serwerem internetowym a aplikacjami internetowymi był CGI (Common Gateway Interface[cgi] . Głównym problemem z CGI jest to, że tworzy on nowe procesy dla każdego żądania HTTP. Jeśli aplikacja jest napisana w języku interpretowanym, każde żądanie HTTP obsługiwane przez skrypty CGI rozpoczyna nową instancje interpretera. Jest to wolne i należy tego unikać w środowisku produkcyjnym. Ponadto CGI może obsługiwać tylko proste odpowiedzi. Nie może obsługiwać, na przykład, strumieniowania plików.

W web2py zawarty jest plik cgihandler.py dla sprzężenia z CGI.

Jednym z rozwiązań problemu jest wykorzystanie modułu mod_python dla Apache. Omawiamy go tutaj, ponieważ moduł ten ciągle jest jeszcze bardzo popularny, chociaż projekt mod_python przestał być oficjalnie rozwijany przez Apache Software Foundation. Moduł mod_python rozpoczyna jedną instancję interpretera Python podczas uruchomienia Apache i obsługuje każde żądanie HTTP we własnym wątku bez konieczności restartu Pythona za każdym razem. Jest to lepsze rozwiązanie niż CGI, ale nie jest optymalne, ponieważ mod_python używa własnego interfejsu dla komunikacji pomiędzy serwerem internetowym a aplikacja internetową. W mod_python wszystkie hostowane aplikacje uruchamiane są pod tym samym identyfikatorem użytkownika i grupy, co powoduje problemy bezpieczeństwa.

W web2py zawarty jest jest plik modpythonhandler.py dla sprzężenia z mod_python.

W ciągu ostatnich lat, popularność zyskuje nowy standard interfejsu dla komunikacji pomiędzy serwerem internetowym a aplikacjami internetowymi napisanym w Pythonie. Nosi on nazwę Web Server Gateway Interface (WSGI)[wsgi-w] [wsgi-o] . Platforma web2py została zbudowana na WSGI i dostarcza obsługę dla używania innych interfejsów, gdy WSGI nie jest dostępne.

Apache obsługuje WSGI poprzez moduł mod_wsgi[modwsgi] zaprojektowany przez Grahama Dumpletona.

W web2py zawarty jest plik wsgihandler.py do sprzężenia z WSGI.

Niektóre usługi hostingowe nie obsługują mod_wsgi. W takim przypadku, trzeba wykorzystać Apache jako proxy i przekazywać wszystkie przychodzące żądania do wbudowanego w web2py serwera internetowego (uruchamiając go na hoście localhost:8000).

W obu przypadkach, z mod_wsgi jak i mod_proxy, Apache może zostać skonfigurowany do serwowania statycznych plików i wykorzystywania bezpośrednio szyfrowania SSL, odciążając w tym web2py.

Nginx używa uWSGI zamiast WSGI, podobny ale inny protokół, który wymaga własnego adaptera.

Serwer internetowy Lighttpd nie obsługuje na razie interfejsu WSGI, ale obsługuje interfejs FastCGI[fastcgi], który jest modyfikacją CGI. Głównym celem FastCGI jest zmniejszenie narzutu związanego ze sprzęgnięciem serwera internetowego z programami CGI, umożliwiając obsługę większej ilości żądań HTTP na raz.

Według strony internetowej Lighttpd, "Lighttpd obsługuje kilka popularnych stron Web 2.0, takich jak YouTube i Wikipedia. Jego szybka infrastruktura IO pozwala na łatwe skalowanie witryn wielokrotnie lepiej niż alternatywnych serwerów internetowych, na tym samym sprzecie". Lighttpd z FastCGI jest w rzeczywistości szybszy niż Apache z mod_wsgi.

W web2py zawarty jest plik fcgihandler.py do sprzężenia z FastCGI.

Platforma web2py zawiera również plik gaehandler.py do sprzężenia z Google App Engine (GAE). Aplikacje internetowe na GAE są uruchamiane "w chmurze". Oznacza to, że framework całkowicie abstrahuje szczegóły sprzętowe. Aplikacja internetowa jest automatycznie replikowana, tyle razy jak to jest konieczne do obsłużenia wszystkich jednoczesnych żądań. Replikowanie w tym przypadku znaczy więcej niż wielowątkowość na pojedynczym serwerze, oznacza to też wiele procesów na różnych serwerach. GAE osiąga ten poziom skalowalności przez blokowanie zapisu do systemu plików i wymóg przechowywania informacji w magazynie danych Google BigTable lub w memcache.

Na platformach innych niż GAE, skalowalność jest problemem, który trzeba rozwiązać samemu, co może wymagać jakichś usprawnień w aplikacjach web2py. Najpopularniejszym sposobem osiągania skalowalności jest użycie wielu serwerów internetowych za load-balancerem (prosty "round robin" lub coś bardziej zaawansowanego: odbieranie informacji zwrotnej "pulsu" z serwerów).

Nawet jeśli istnieje wiele serwerów internetowych, to musi istnieć jeden i tylko jeden serwer bazy danych. Domyślnie, web2py wykorzystuje system plików do przechowywania sesji, biletów błędów, plików przesłanych na serwer i pamięci podręcznej. Oznacza to, że w domyślnej konfiguracji muszą być udostępnione odpowiednie foldery.

image

W dalszej części tego rozdziału, rozpatrujemy różne receptury związane z bardziej zaawansowanym podejściem do tego zagadnienia, w tym:

  • przechowywanie sesji w bazie danych, w pamięci podręcznej lub nieprzechowywanie sesji w ogóle;
  • przechowywanie biletóww lokalnym systemie plików i przeniesienie ich do bazy danych w partiach;
  • użycie memcache zamiast cache.ram i cache.disk;
  • przechowywanie plików w bazie danych zamiast we współdzielonym systemie plików.

Chociaż trzy pierwsze receptury są zalecane, to czwarta może przynieść korzyści, głównie w przypadku małych plików, ale przy dużych plikach może przynieść odwrotny skutek.

Skrypt anyserver.py

anyserver
bjoern
cgi
cherrypy
diesel
eventlet
fapws
flup
gevent
gunicorn
mongrel2
paste
tornado
twisted
wsgiref

W web2py zawarty jest plik o nazwie anyserver.py implementujący interfejs WSGI dla następujących popularnych serwerów: bjoern, cgi, cherrypy, diesel, eventlet, fapws, flup, gevent, gunicorn, mongrel2, paste, rocket, tornado, twisted, wsgiref.

Można zastosować każdy z tych serwerów, na przykład Tornado, po prostu robiąc tak:

python anyserver.py -s tornado -i 127.0.0.1 -p 8000 -l -P

Tutaj -l jest flagą rejestrowania a -P flagą profilera. Więcej informacji o opcjach linii poleceń można uzyskać używajac flagę "-h":

python anyserver.py -h

Linux i Unix

Pierwsze kroki wdrożenia produkcyjnego

Oto kilka kroków do zainstalowania od podstaw apache+python+mod_wsgi+web2py+postgresql.

Na Ubuntu:

wget https://raw.githubusercontent.com/web2py/web2py/master/scripts/setup-web2py-ubuntu.sh
chmod +x setup-web2py-ubuntu.sh
sudo ./setup-web2py-ubuntu.sh

Na Fedora:

wget https://raw.githubusercontent.com/web2py/web2py/master/scripts/setup-web2py-fedora.sh
chmod +x setup-web2py-fedora.sh
sudo ./setup-web2py-fedora.sh

Oba skrypty powinny działać zaraz po ich pobraniu, ale każda instalacja Linux jest trochę inna, więc przed ich uruchomieniem trzeba sprawdzić kod źródłowy. W przypadku Ubuntu, wiekszość tego co skrypty te robią, jest opisane poniżej. Nie implementują one optymalizacji skalowalności, co jest opisane dalej.

Konfiguracja Apache

W tym rozdziale używamy Ubuntu Server Edition jako system operacyjny. Polecenia konfiguracyjne są bardzo podobne do poleceń wszystkich innych dystrybucji opartych na Debian, ale są inne niż w systemach opartych na Fedora (które używają yum zamiast apt-get). Można użyć Apache w wersji 2.2.x albo 2.4.x

Po pierwsze upewnij się, że są zainstalowane wszystkie niezbędne pakiety Pythona i Apache wpisując następujące polecenia powłoki:

sudo apt-get update
sudo apt-get -y upgrade
sudo apt-get -y install openssh-server
sudo apt-get -y install python
sudo apt-get -y install python-dev
sudo apt-get -y install apache2
sudo apt-get -y install libapache2-mod-wsgi
sudo apt-get -y install libapache2-mod-proxy-html

Włącz w Apache moduły SSL, proxy i WSGI:

sudo ln -s /etc/apache2/mods-available/proxy_http.load /etc/apache2/mods-enabled/proxy_http.load
sudo a2enmod ssl
sudo a2enmod proxy
sudo a2enmod proxy_http
sudo a2enmod wsgi

Utwórz folder SSL i wstaw tam certfikaty SSL:

sudo mkdir /etc/apache2/ssl

Należy uzyskać swoje certyfikaty SSL od zaufanego Certificate Authority, takiego jak verisign.com, ale dla celów testowych wystarczy wygenerować certyfikaty z własnym podpisem, zgodnie z instrukcją[openssl].

Zrestartuj serwer Apache:

sudo /etc/init.d/apache2 restart

Pliki konfiguracyjne Apache znadują się w katalogu:

/etc/apache2/sites-available/default

a pliki dzienników Apache w:

/var/log/apache2/

Moduł mod_wsgi

Najpierw pobierz źródło web2py i rozpakuj ten pakiet na komputerze na którym została wykonana popoprzednio omówiona instalacja serwera internetowego.

Pakiet źródłowy web2py trzeba umieścić gdzieś poza katalogiem głównym dokumentów serwera Apache, na przykład w katalogu /home/www-data/ i ustawić własność tego katalogu dla dla właściciela procesu serwera Apache (w dystrybucjach Debiana i Ubuntu jest to użytkownik www-data i grupa www-data). Czynności te mogą być wykonane przez następujące polecania powłoki:

cd /home/www-data/
sudo wget http://web2py.com/examples/static/web2py_src.zip
sudo unzip web2py_src.zip
sudo chown -R www-data:www-data /home/www-data/web2py

W celu ustawienia web2py z modułem mod_wsgi, utwórz nowy plik konfiguracyjny Apache:

/etc/apache2/sites-available/web2py.conf

i wstaw w nim następujący kod:

<VirtualHost *:80>
  ServerName web2py.example.com
  WSGIDaemonProcess web2py user=www-data group=www-data display-name=%{GROUP}
  WSGIProcessGroup web2py
  WSGIScriptAlias / /home/www-data/web2py/wsgihandler.py

#This is Apache 2.2.x permission syntax. See Apache docs for 2.4 syntax
# http://httpd.apache.org/docs/2.4/upgrading.html#run-time

  <Directory /home/www-data/web2py>
    AllowOverride None
    Order Allow,Deny
    Deny from all
    <Files wsgihandler.py>
      Allow from all
    </Files>
  </Directory>

  AliasMatch ^/([^/]+)/static/(?:_[\d]+.[\d]+.[\d]+/)?(.*)   /home/www-data/web2py/applications/$1/static/$2
  <Directory /home/www-data/web2py/applications/*/static/>
    Order Allow,Deny
    Allow from all
  </Directory>

  <Location /admin>
  Deny from all
  </Location>

  <LocationMatch ^/([^/]+)/appadmin>
  Deny from all
  </LocationMatch>

  CustomLog /private/var/log/apache2/access.log common
  ErrorLog /private/var/log/apache2/error.log
</VirtualHost>

Po ponownym uruchomieniu Apache, kod ten powinien przekazywać wszystkie żądania do web2py bez wykorzystywania serwera WSGI Rocket.

Przeniesienie skryptu handlera

Na koniec, trzeba zmienić lokalizację skryptu handlera web2py/handlers/wsgihandler.py. Tak jak udokumentowano w katalogu handlerów, skrypt ten powinien zostać przeniesiony lub skopiowany do katalogu nadrzędnego (czyli do tego samego katalogu, gdzie znajduje się skrypt web2py.py). Odwołanie symboliczne może spowodować problemy z uprawnieniami Apache.

Niektóre aspekty WSGI

Oto kilka wyjaśnień:

WSGIDaemonProcess web2py user=www-data group=www-data display-name=%{GROUP}

określa grupę procesu demona w kontekście "web2py.example.com". Definiując to wewnątrz wirtualnego hosta umożliwia się, że host ten uzyskuje dostęp do stosowania WSGIProcessGroup - dotyczy to każdego wirtualnego hosta z tą sama nazwą serwera, ale na innym porcie.

Opcje "user" i "group" należy ustawić na użytkownika i grupę majace prawa dostępu do katalogu, w którym skonfigurowany został web2py. Nie trzeba ustawiać opcji "user" i "group", jeśli katalog instalacyjny web2py jest zapisywalny przez właściciela procesu Apache.

Opcja "display-name" sprawia, że w wyjściu ps pojawia się nazwa procesu jako "(wsgi-web2py)", zamiast jako wykonywalna nazwa serwera Apache.

Ponieważ żadna z opcji "processes" czy "threads" nie jest określona, grupa procesu demona będzie miała przydzielony pojedynczy proces z 15 wątkami uruchomionymi w tym procesie. Jest to więcej niż wystarczające dla większości witryn i nie trzeba tego zmieniać.

Jeśli się to nadpisze, nie należy używać "processes=1", ponieważ w ten sposób wyłącza się wszystkie narzędzia debugowania WSGI w przeglądarce, które sprawdzają flagę "wsgi.multiprocess". Dzieje się tak dlatego, że każde użycie opcji "processes" powoduje, że flaga ta jest ustawiana na True, nawet dla pojedynczego procesu a wspomniane narzędzia oczekują ustawienia tej flagi na False.

Uwaga: jeśli kod aplikacji lub zewnętrzengo modułu rozszerzajacego nie jest wątkowo bezpieczny, trzeba zamiast tego użyć opcji processes=5 threads=1. Tworzy to pięć grup procesów, gdzie każdy proces jest pojedynczym wątkiem. Można rozważyć użycie maximum-requests=1000, jeśli aplikacja ujawnia obiekty Pythona, ponieważ nie jest w stanie właściwie odśmiecać pamięć podręczną.

WSGIProcessGroup web2py

deleguje uruchamianie wszystkich aplikacji WSGI do demona grupy procesów, który został skonfigurowany przy użyciu dyrektywy WSGIDaemonProcess.

WSGIScriptAlias / /home/www-data/web2py/wsgihandler.py

montuje aplikację web2py. W tym przypadku jest ona montowana w katalogu głównym witryny internetowej.

Fragment:

<Directory /home/www-data/web2py>
  ...
</Directory>

nadaje uprawnienia dostępu dla Apache do pliku skryptu WSGI.

<Directory /home/www-data/web2py/applications/*/static/>
  Order Allow,Deny
  Allow from all
</Directory>

instruuje Apache aby ominął web2py podczas wyszukiwania plików statycznych.

<Location /admin>
  Deny from all
</Location>

i

<LocationMatch ^/([^/]+)/appadmin>
  Deny from all
</LocationMatch>

blokuje publiczny dostęp do admin i appadmin

Zwykle chcemy po prostu nadać uprawnienia do całego katalogu, gdzie są zlokalizowane pliki skryptów WSGI, ale web2py umieszcza plik skryptu WSGI w katalogu, w którym zawarty jest też inny kod żródłowy, w tym hasło interfejsu administracyjnego. Otwarcie całego tego katalogu będzie stwarzać problemy bezpieczeństwa, ponieważ technicznie Apache będzie udzielał uprawnień do obsługi wszystkich plików przez każdego użytkownika, który dostanie się do tego katalogu poprzez odwzorowany adres URL.

Dla uniknięcia tych problemów, trzeba jawnie zabronić dostępu do zawartości tego katalogu, z wyjatkiem pliku skryptu WSGI i zabronić użytkownikowi wykonywania jakiego kolwiek przesłaniania konfiguracji poprzez plik .htaccess, co podniesie znacznie poziom bezpieczeństwa.

Kompletna i skomentowana konfiguracja WSGI na serwerze Apache znajduje się w pliku konfiguracyjnym:

scripts/web2py-wsgi.conf

Ten rozdział został stworzony z pomocą Grahama Dumpletona, twórcy mod_wsgi.

Ustawienie hasła

W środowisku produkcyjnym może okazać się konieczne ustawinie hasła administratora programowo. Można to zrobić w powłoce Bash, takim poleceniem:

sudo -u www-data python -c "from gluon.main import save_password; save_password(raw_input('admin password: '),443)"

Moduł mod_wsgi a SSL

W celu wymuszenia na jakichś aplikacjach (na przykład admin i appadmin) komunikacji w protokole HTTPS, zapisz plik certyfikatu i klucza SSL w lokalizacji:

/etc/apache2/ssl/server.crt
/etc/apache2/ssl/server.key

oraz edytuj plik konfiguracyjny web2py.conf i dodaj tam:

<VirtualHost *:443>
  ServerName web2py.example.com
  SSLEngine on
  SSLCertificateFile /etc/apache2/ssl/server.crt
  SSLCertificateKeyFile /etc/apache2/ssl/server.key

  WSGIProcessGroup web2py

  WSGIScriptAlias / /home/www-data/web2py/wsgihandler.py

  <Directory /home/www-data/web2py>
    AllowOverride None
    Order Allow,Deny
    Deny from all
    <Files wsgihandler.py>
      Allow from all
    </Files>
  </Directory>

  AliasMatch ^/([^/]+)/static/(.*)         /home/www-data/web2py/applications/$1/static/$2

  <Directory /home/www-data/web2py/applications/*/static/>
    Order Allow,Deny
    Allow from all
  </Directory>

  CustomLog /var/log/apache2/access.log common
  ErrorLog /var/log/apache2/error.log

</VirtualHost>

Uruchom ponownie Apache i upewnij się, że masz dostęp do aplikacji poprzez HTTPS:

https://www.example.com/admin
https://www.example.com/examples/appadmin
http://www.example.com/examples

i że dostęp poprzez HTTP nie działa:

http://www.example.com/admin
http://www.example.com/examples/appadmin

Moduł mod_proxy

Niektóre dystrybucje Unix/Linux mogą uruchamiać Apache, ale nie obsługują mod_wsgi. W takim przypadku najprostszym rozwiązaniem jest uruchomienie Apache jako proxy i powierzenie Apache tylko obsługi plików statycznych.

Oto minimalna konfiguracja Apache:

NameVirtualHost *:80
#### deal with requests on port 80
<VirtualHost *:80>
   Alias / /home/www-data/web2py/applications
   ### serve static files directly
   <LocationMatch "^/welcome/static/.*">
    Order Allow, Deny
    Allow from all
   </LocationMatch>
   ### proxy all the other requests
   <Location "/welcome">
     Order deny,allow
     Allow from all
     ProxyRequests off
     ProxyPass http://localhost:8000/welcome
     ProxyPassReverse http://localhost:8000/
     ProxyHTMLURLMap http://127.0.0.1:8000/welcome/ /welcome
   </Location>
   LogFormat "%h %l %u %t "%r" %>s %b" common
   CustomLog /var/log/apache2/access.log common
</VirtualHost>

Powyższy skrypt udostępnia tylko aplikację "welcome". Dla udostępnienia innych aplikacji należy dodać odpowiedni blok <Location>...</Location> z taką samą składnią jak zostało to zrobione w aplikacji "welcome".

Skrypt ten zakłada, że serwer web2py jest uruchomiony na porcie 8000. Przed zrestartowaniem Apache, upewnij sie, że tak jest:

nohup python web2py.py -a '<recycle>' -i 127.0.0.1 -p 8000 &

Można określić hasło stosując opcję -a lub zamiast hasła parametr "<recycle>". W tym drugim przypadku, wykorzystywane jest wcześniej zapisane hasło i nie jest ono przechowywane w historii powłoki.

Można również użyć parametr "<ask>", aby wyświetlać monit o hasło.

Użycie polecenia nohup sprawia, że proces serwera nie zostaje zniszczony po zamknięciu okna powłoku. Polecenie nohup rejestruje wszystkie dane wyjściowe w pliku nohup.out.

W celu wymuszenia komunikacji w aplikacjach *admin* i *appadmin* poprzez HTTPS, trzeba użyć następującego wpisu w pliku konfigiracyjnym Apache:

NameVirtualHost *:80
NameVirtualHost *:443
#### deal with requests on port 80
<VirtualHost *:80>
   Alias / /home/www-data/web2py/applications
   ### admin requires SSL
   <LocationMatch "^/admin">
     SSLRequireSSL
   </LocationMatch>
   ### appadmin requires SSL
   <LocationMatch "^/welcome/appadmin/.*">
     SSLRequireSSL
   </LocationMatch>
   ### serve static files directly
   <LocationMatch "^/welcome/static/.*">
     Order Allow,Deny
     Allow from all
   </LocationMatch>
   ### proxy all the other requests
   <Location "/welcome">
     Order deny,allow
     Allow from all
     ProxyPass http://localhost:8000/welcome
     ProxyPassReverse http://localhost:8000/
   </Location>
   LogFormat "%h %l %u %t "%r" %>s %b" common
   CustomLog /var/log/apache2/access.log common
</VirtualHost>
<VirtualHost *:443>
   SSLEngine On
   SSLCertificateFile /etc/apache2/ssl/server.crt
   SSLCertificateKeyFile /etc/apache2/ssl/server.key
   <Location "/">
     Order deny,allow
     Allow from all
     ProxyPass http://localhost:8000/
     ProxyPassReverse http://localhost:8000/
   </Location>
   LogFormat "%h %l %u %t "%r" %>s %b" common
   CustomLog /var/log/apache2/access.log common
</VirtualHost>

Interfejs administracyjny musi być wyłączony gdy web2py działa na skróconym hoście z mod_proxy, w przeciwnym razie będzie dostępny dla innych użytkowników.

Uruchamianie web2py jako demona Linux

Jeżeli korzysta się z mod_wsgi, można skonfigurować serwer web2py, tak aby mógł być uruchamiany, zatrzymywany i uruchamiany ponownie jako demon Linuxa a więc mógłby być uruchamiany automatycznie na etapie rozruchu komputera.

Procedura takiej konfiguracji zależy od dystrybucji systemu Linux/Unix.

W folderze web2py znajdują się dwa skrypty, które można wykorzystać w tym celu:

scripts/web2py.ubuntu.sh
scripts/web2py.fedora.sh

Na Ubuntu lub systemach opartych na Debianie trzeba edytować plik "web2py.ubuntu.sh" i zamienić ścieżkę "/usr/lib/web2py" na ścieżkę własnej instalacji web2py, następnie uruchomić poniższe polecenie powłoki, aby przenieść ten plik do właściwego folderu, zarejestrować go w usłudze startup i uruchomić:

sudo cp scripts/web2py.ubuntu.sh /etc/init.d/web2py
sudo update-rc.d web2py defaults
sudo /etc/init.d/web2py start

Na Fedora i dystrybucjach opartych na Fedora trzeba edytować plik "web2py.fedora.sh" i zamienić ścieżkę "/usr/lib/web2py" na ścieżkę swojej instalacji web2py, następnie uruchomić poniższe polecenia powłoki, aby przenieść ten plik do właściwego folderu, zarejestrować go w usłudze startup i uruchomić:

sudo cp scripts/web2py.fedora.sh /etc/rc.d/init.d/web2pyd
sudo chkconfig --add web2pyd
sudo service web2py start

Nginx

Nginx jest darmowym, otwarto-źródłowym serwerem internetowym, który zyskuje coraz większą popularność, ze względu na swoje znakomite osiągi.

W przeciwieństwie do tradycyjnych serwerów, Nginx nie obsługuje wątków. Zamiast tego wykorzystuje asynchroniczną, napędzaną zdarzeniami architekturę do obsługi współbieżności. Architektura ta skutkuje małym i przewidywalnym zużyciem pamięci, nawet przy dużym obciążeniu.

Nginx to coś więcej niż tylko serwer HTTP czy serwer proxy - jest to również serwer proxy IMAP/POP3.

Nginx jest łatwiejszy w konfiguracji a jego pliki konfiguracyjne są prostsze i bardziej kompaktowe niż odpowiadające im pliki Apache.

Nginx nie obsługuje WSGI, ale dostarcza natywną obsługę protokołu uWSGI [uwsgi].

uwsgi

Na Ubuntu można zainstalować Nginx poleceniem:

apt-get -y install nginx-full

Następnie trzeba utworzyć plik konfiguracyjny, jak następuje:

# file /etc/nginx/sites-available/web2py
server {
        listen          80;
        server_name     $hostname;
        #to enable correct use of response.static_version
        #location ~* /(\w+)/static(?:/_[\d]+.[\d]+.[\d]+)?/(.*)$ {
        #    alias /home/www-data/web2py/applications/$1/static/$2;
        #    expires max;
        #}
        location ~* /(\w+)/static/ {
            root /home/www-data/web2py/applications/;
            #remove next comment on production
            #expires max;
        }
        location / {
            #uwsgi_pass      127.0.0.1:9001;
            uwsgi_pass      unix:///tmp/web2py.socket;
            include         uwsgi_params;
            uwsgi_param     UWSGI_SCHEME $scheme;
            uwsgi_param     SERVER_SOFTWARE    nginx/$nginx_version;
        }
}
server {
        listen 443 default_server ssl;
        server_name     $hostname;
        ssl_certificate         /etc/nginx/ssl/web2py.crt;
        ssl_certificate_key     /etc/nginx/ssl/web2py.key;
        ssl_prefer_server_ciphers on;
        ssl_session_cache shared:SSL:10m;
        ssl_session_timeout 10m;
        ssl_ciphers ECDHE-RSA-AES256-SHA:DHE-RSA-AES256-SHA:DHE-DSS-AES256-SHA:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA;
        ssl_protocols SSLv3 TLSv1;
        keepalive_timeout    70;
        location / {
            #uwsgi_pass      127.0.0.1:9001;
            uwsgi_pass      unix:///tmp/web2py.socket;
            include         uwsgi_params;
            uwsgi_param     UWSGI_SCHEME $scheme;
            uwsgi_param     SERVER_SOFTWARE    nginx/$nginx_version;
        }

}

Trzeba też stworzyć odwołanie symboliczne do pliku i usunąć odwołanie symboliczne do pliku default:

ln -s /etc/nginx/sites-available/web2py /etc/nginx/sites-enabled/web2py
rm /etc/nginx/sites-enabled/default

Być może trzeba też utworzyć folder ssl dla certyfikatów i wstawić tam certyfikaty:

mkdir /etc/nginx/ssl
cp web2py.key /etc/nginx/ssl
cp web2py.crt /etc/nginx/ssl

Następnie trzeba zainstalować i skonfigurować uWSGI:

sudo mkdir /etc/uwsgi
sudo mkdir /var/log/uwsgi

oraz utworzyć plik konfiguracyjny "/etc/uwsgi/web2py.xml":

<uwsgi>
    <socket>/tmp/web2py.socket</socket>
    <pythonpath>/home/www-data/web2py/</pythonpath>
    <mount>/=wsgihandler:application</mount>
    <master/>
    <processes>4</processes>
    <harakiri>60</harakiri>
    <reload-mercy>8</reload-mercy>
    <cpu-affinity>1</cpu-affinity>
    <stats>/tmp/stats.socket</stats>
    <max-requests>2000</max-requests>
    <limit-as>512</limit-as>
    <reload-on-as>256</reload-on-as>
    <reload-on-rss>192</reload-on-rss>
    <uid>www-data</uid>
    <gid>www-data</gid>
    <no-orphans/>
</uwsgi>

Zakładamy tu, że web2py jest zainstalowany w katalogu "/home/www-data/web2py", tak jak miało to miejsce w przypadku Apache.

Trzeba też edytować drugi plik konfiguracyjny "/etc/init/uwsgi-emperor.conf":

# Emperor uWSGI script
description "uWSGI Emperor"
start on runlevel [2345]
stop on runlevel [06]
respawn
exec uwsgi --master --die-on-term --emperor /etc/uwsgi --logto /var/log/uwsgi/uwsgi.log

Na koniec zrestartuj wszystko:

start uwsgi-emperor
/etc/init.d/nginx restart

Można przeładować uwsgi poleceniem:

restart uwsgi-emperor

Proces uwsgi zatrzymuje się poleceniem:

stop uwsgi-emperor

Można przeładować tylko sam web2py (bez restartowania uwsgi) poleceniem:

touch /etc/uwsgi/web2py.xml

Te wszystkie czynności są wykonywane automatycznie przez skrypty dostarczone w web2py:

scripts/setup-web2py-nginx-uwsgi-on-centos.sh
scripts/setup-web2py-nginx-uwsgi-ubuntu.sh

Lighttpd

Lighttpd
FastCGI
fcgihandler

Lighttpd można zainstalować na Ubuntu lub innych dystrybucjacj opartych na Debianie poprzez polecenie:

apt-get -y install lighttpd

Po instalacji trzeba edytować /etc/rc.local i utworzyć konfigurację procesu fcgi dla web2py:

cd /var/www/web2py && sudo -u www-data nohup python fcgihandler.py &

Następnie edytować plik konfiguracyjny Lighttpd:

/etc/lighttpd/lighttpd.conf

tak, aby można było odnaleźć gniazdo tworzone przez powyższy proces. W pliku konfiguracyjnym trzeba napisać coś takiego:

server.modules              = (
        "mod_access",
        "mod_alias",
        "mod_compress",
        "mod_rewrite",
        "mod_fastcgi",
        "mod_redirect",
        "mod_accesslog",
        "mod_status",
)

server.port = 80
server.bind = "0.0.0.0"
server.event-handler = "freebsd-kqueue"
server.error-handler-404 = "/test.fcgi"
server.document-root = "/home/www-data/web2py/"
server.errorlog      = "/tmp/error.log"

fastcgi.server = (
  "/handler_web2py.fcgi" => (
      "handler_web2py" => ( #name for logs
         "check-local" => "disable",
         "socket" => "/tmp/fcgi.sock"
      )
   ),
)

$HTTP["host"] = "(^|.)example.com$" {
 server.document-root="/home/www-data/web2py"
    url.rewrite-once = (
      "^(/.+?/static/.+)$" => "/applications$1",
      "(^|/.*)$" => "/handler_web2py.fcgi$1",
    )
}

Po czym sprawdzamy błędy składni:

lighttpd -t -f /etc/lighttpd/lighttpd.conf

i uruchamiamy (lub restartujemy) serwer internetowy poleceniem:

/etc/init.d/lighttpd restart

Proszę mieć na uwadze, że FastCGI wiąże serwer web2py do gniazda Unix, a nie do gniazda IP.

/tmp/fcgi.sock

To lokalizacja gdzie Lighttpd przekazuje żądania HTTP i z której otrzymuje odpowiedzi. Gniazda Unix są lżejsze niż gniazda internetowe i dlatego Lighttpd+FastCGI+web2py jest szybszy. Podobnie jak w przypadku Apache, jest możliwe skonfigurowanie Lighttpd do bezpośredniej obsługi plików statycznych i wymuszania aolikacji poprzez HTTPS. Proszę zapoznać się z dokumentacją Lighttpd w celu poznania szczegółów.

Przykłady w tym rozdziale pochodzą z wpisu Johna Heenana na web2pyslices.

Gdy web2py działa na współdzielonym hoście z obsługą FastCGI, trzeba wyłączyć interfejs administracyjny, w przeciwnym razie będzie on dostępny dla innych użytkowników.

Współdzielony hosting z mod_python

Zdarza się, szczególnie na hostach współdzielonych, że nie ma się uprawnień do bezpośredniego konfigurowania pliku konfiguracyjnego Apache. W czasie pisania tego artykułu na większości takich hostów nadal dostępny był mod_python, nawet jeśli nie był uruchomiony mod_wsgi.

W takiej sytuacji można uruchamiać web2py. Pokazujemy tutaj przykład konfiguracji takiego przypadku.

Umieszczamy zawartość web2py w folderze "htdocs".

W folderze web2py tworzymy plik "web2py_modpython.py" z następującą zawartością:

from mod_python import apache
import modpythonhandler

def handler(req):
    req.subprocess_env['PATH_INFO'] = req.subprocess_env['SCRIPT_URL']
    return modpythonhandler.handler(req)

Tworzymy lub aktualizujemy plik ".htaccess" wprowadzając w nim następującą zawartość:

SetHandler python-program
PythonHandler web2py_modpython
#PythonDebug On

Przykład ten został dostarczony przez Niktara.

Cherokee z FastCGI

Cherokee
FastCGI
Cherokee to bardzo szybki serwer internetowy i podobnie jak web2py, dostarcza interfejs internetowy z obsługą AJAX dla swojej konfiguracji. Jest to interfejs napisany w Pythonie. Ponadto nie wymaga ponownego uruchamiania po wiekszości zmian konfiguracji.

Oto procedura konfiguracji web2py z Cherokee:

Pobierz Cherokee[cherokee]

Rozpakuj pakiet, skompiluj kod źródłowy i zainstaluj:

tar -xzf cherokee-0.9.4.tar.gz
cd cherokee-0.9.4
./configure --enable-fcgi && make
make install

Uruchom web2py co najmniej raz, aby sprawdzić, że stworzony został folder "applications".

Napisz skrypt o nazwie "startweb2py.sh" z następującym kodem:

#!/bin/bash
cd /var/web2py
python /var/web2py/fcgihandler.py &

i nadaj temu skryptowi uprawnienia do wykonania i uruchamiania. Skrypt ten uruchamia web2py pod kontrolą hamdlera FastCGI.

Uruchom Cherokee i cherokee-admin:

sudo nohup cherokee &
sudo nohup cherokee-admin &

Domyślnie cherokee-admin nasłuchuje tylko na lokalnym interfejsie na porcie 9090. Nie stanowi to problemu, jeśli ma się pełny, fizyczny dostęp do komputera. W przeciwnym przypadku, można wymusić wiązanie do adresu IP i portu używając następujących opcji:

-b,  --bind[=IP]
-p,  --port=NUM

lub wykonując przekazanie portu SSH (bardziej bezpieczniejsze i zalecane):

ssh -L 9090:localhost:9090 remotehost

Otwórz w przeglądarce adres "http://localhost:9090". Jeśli wszystko jest w porządku, można zająć się cherokee-admin.

W interfejsie administracyjnym cherokee-admin kliknij "info sources". Wybierz "Local Interpreter". Wpisz następujący kod i kilknij "Add New".

Nick: web2py
Connection: /tmp/fcgi.sock
Interpreter: /var/web2py/startweb2py.sh

Wreszcie wykonaj następujące czynności końcowe:

  • Kliknik "Virtual Servers", następnie "Default".
  • Kliknij "Behavior", następnie dostępną tu opcje "default".
  • Z pola listy wybierz "FastCGI" zamiast "List and Send".
  • Na dole okna wybierz "web2py" jako "Application Server"
  • Zaznacz wszystkie pola wyboru (można pozostawić Allow-x-sendfile). Jeśli pojawi się ostrzeżenie, spróbuj wyłączać i włączać poszczególne pola wyboru. To automatycznie zatwierdzi ponownie parametr serwera aplikacji. Wyświetlenie ostrzeżenia, nie zawsze wiąże się z błędem.
  • Wpisz w przeglądarce adres "http://yoursite" - powinna się pojawić aplikacja "Welcome to web2py".

PostgreSQL

PostgreSQL jest darmową i otwarto-źródłowym serwerem bazy danych, który jest używany w wymagającym środowisku produkcyjnym, na przykład, do przechowywania bazy danych nazw domen .org i okazał się on dobrze skalowalny nawet do setek terabaijtów danych. Ma bardzo szybką i solidną obsługę transakcji i zapewnia samoczyszczące funkcje, odciążające administratora od wielu zadań związanych z obsługą bazy danych.

Na Ubuntu i innych dystrybucjach opartych na Debianie, najłatwiej zainstalować PostgreSQL i jego API Pythona poleceniami:

sudo apt-get -y install postgresql
sudo apt-get -y install python-psycopg2

Pakiet instalacyjny potrafi uruchomić serwer internetowy (serwery internetowe) i serwer bazy danych na różnych komputerach. W takim przypadku, komputery na których uruchomiono serwery internetowe i serwer bazy danych powinny być połączone w bezpieczną wewnętrzną (fizyczną) sieć lub poprzez tunel SSL.

Po zainstalowaniu PostgreSQL trzeba edytować plik konfiguracyjny:

sudo nano /etc/postgresql/9.1/main/postgresql.conf

i upewnić się, że zawiera on następujące linie kodu:

...
listen_addresses = 'localhost'
...
track_counts = on
...
autovacuum = on   # Enable autovacuum subprocess?  'on'
...

Po czym proszę edytować plik uwierzytelniania klienta PostgreSQL

sudo nano /etc/postgresql/9.1/main/pg_hba.conf

i zmienić metodę uwierzytelniania na trust:

...
# "local" is for Unix domain socket connections only
local   all             all                                     trust
# IPv4 local connections:
host    all             all             127.0.0.1/32            trust
# IPv6 local connections:
host    all             all             ::1/128                 trust
...

Teraz należy zrestartować serwer bazy danych:

sudo /etc/init.d/postgresql restart

Podczas ponownego uruchomienia serwera PostgreSQL powinien on powiadomić o porcie, na którym jest uruchomiony. Standardowym portem PostgreSQL jest 5432.

PostgreSQL rejestruje zdarzenia w katalogu:

/var/log/postgresql/

Gdy już uruchomiony jest serwer bazy danych i działa, trzeba utworzyć użytkownika bazy danych i bazę danych, tak aby aplikacje web2py mogły ją używać:

sudo -u postgres createuser -PE -s myuser
postgresql> createdb -O myuser -E UTF8 mydb
postgresql> echo 'The following databases have been created:'
postgresql> psql -l
postgresql> psql mydb

Pierwsze z tych poleceń przyznaje uprawnienia dostępu superuser nowemu użytkownikowi, o nazwie myuser. Wyświetli ono monit o hasło.

Dowolna aplikacja web2py może łączyć się z tą bazą danych poprzez polecenie:

db = DAL("postgres://myuser:mypassword@localhost:5432/mydb")

gdzie mypassword jest ustalonym hasłem użytkownika myuser a 5432, to port na którym uruchomiony jest serwer (klaster) bazy danych.

Zwykle stosuje się odrębną bazę danych dla poszczególnych aplikacji i wiele instancji tej samej aplikacji podłączonych do tej samej bazy danych. Możliwe jest też współdzielenie jednej bazy danych przez kilka aplikacji.

W celu uzyskania informacji o wykonywaniu kopii zapasowych baz danych, proszę zapoznać się z dokumentacją PostgreSQL, w szczególności z poleceniami pg_dump i pg_restore.

Uruchomienie schedulera jako demona Linux

W celu zainstalowania schedulera jako stałego demona Linux (z wykorzystaniem upstart), wstaw poniższy kod do /etc/init/web2py-scheduler.conf, zakładając, że instancja web2py jest zainstalowana w katalogu dokumentów serwera internetowego, uruchamiana z prawami <user>, z aplikacji <myapp>, na interfejsie sieciowym eth0.

description "web2py task scheduler"
start on (local-filesystems and net-device-up IFACE=eth0)
stop on shutdown
respawn limit 8 60 # Give up if restart occurs 8 times in 60 seconds.
exec sudo -u <user> python /home/www-data/web2py/web2py.py -K <myapp>
respawn

Można uruchamiać, restartować, i sprawdzać stan demona odpowiednio poleceniami:

sudo start web2py-scheduler
sudo stop web2py-scheduler
sudo restart web2py-scheduler
sudo status web2py-scheduler

Windows

Apache i mod_wsgi

Instalacja Apache i mod_wsgi pod Windows wymaga nieco innego postępowania. Jednakże, jest to procedura podobna do poprzednio omówionej procedury dla Linux, tak więć proszę się z nią zapoznać.

Zakładamy tu, że został już zainstalowany pakiet binarny Python 2.x dl Windowa, i ma się uruchomioną źródłową wersję web2py, zlokalizowaną w c:/web2py.

Nowoczesne binaria Apache dla Windows (np. 2.4.x) nie są obecnie dostępne na stronie apache.org. Zamiast tego, można je pobrać ze stron partnerskich, takich jak ApacheHaus. Na stronie Apache dostępna jest pełna lista partnerów - wyszukaj tam binaria Apache 2.4 dla Windows.

Jednak binaria Windows nie zawierają mudułu WSGI. Proszę odwiedzić stronę modwsgi (obecnie na http://code.google.com/p/modwsgi/) i pobrać wstępnie skompilowane binaria dla swojej wersji Pythona i Apache. Po zainstalowaniu Apache, trzeba umieścić biblioteke .so w katalogu modułów.

W celu umożliwienia ładowania modułu WSGI trzeba zmodyfikować plik httpd.conf. Oto przykład:

LoadModule wsgi_module modules/mod_wsgi.so

Wiki modwsgi omawia specyfikację Windows - polecamy jej przeczytanie.

Plik httpd.conf musi zostać skonfigurowany jak przy każdej nowej instalacji Apache.

Instalacja certyfikatów SSL jest taka sama dla Windows jak dla Linux.

Pakiet binarny Windows jest najczęściej skonfigurowany do ładowania wstępnie skonfigurowanych modułów SSL (pakiet Apache Haus właśnie takim jest).

Aplikacje web2py powinny być serwowane na protokole HTTPS, choć możliwe jest też opcjonalne skonfigurowanie HTTP. Zazwyczaj oznacza to port 80 i 443, ale porty te moga być zajęte, jeśli w systemie Windows działa też IIS - wówczas trzeba skonfigurować alternatywne porty.

W naszym przykładzie zakładamy wykorzystanie portów 80 i 443. W pliku httpd.conf odszukaj linię z wpisem "Listen 80" i dodaj po niej:

Listen 443

Nastepnie dodaj poniższy kod:

NameVirtualHost *:443
<VirtualHost *:443>
  DocumentRoot "C:/web2py/applications"
  ServerName server1

  <Directory "C:/web2py">
    Order allow,deny
    Deny from all
  </Directory>

  <Location "/">
    Order deny,allow
    Allow from all
  </Location>

  <LocationMatch "^(/[\w_]*/static/.*)">
    Order Allow,Deny
    Allow from all
  </LocationMatch>

  WSGIScriptAlias / "C:/web2py/wsgihandler.py"
#and don't forget to move the handler script out of the handlers directory

  SSLEngine On
#these cert settings are correct for self-signed certificates
  SSLCertificateFile conf/server.crt
  SSLCertificateKeyFile conf/server.key

  LogFormat "%h %l %u %t "%r" %>s %b" common
  CustomLog logs/access.log common
</VirtualHost>

Zapisz plik i sprawdź konfigurację używając opcji menu: [Start > Program > Apache HTTP Server 2.2 > Configure Apache Server > Test Configuration]

Jeśli nie wystąpią problemy na ekranie pojawią się polecenia otwierające i zamykajace. Teraz można uruchomić Apache:

[Start > Program > Apache HTTP Server 2.2 > Control Apache Server > Start]

albo jeszcze lepiej, uruchomić go na monitorze paska zadań:

[Start > Program > Apache HTTP Server 2.2 > Control Apache Server]

Teraz można kliknąć prawym przyciskiem myszy na ikonę pióra, symbolizującą Apache aby udostępnić sobie opcje "Open Apache Monitor", gdzie dostępne są opcje uruchomienia, zatrzymania i zrestartowania Apache.

Ten rozdział został pierwotnie napisany przez Jonathana Lundella.

Użycie nssm do uruchamiania web2py jako usługi Windows

To co w Linux nazywamy demonem, w Windows nazywamy usługą. Wbudowany w web2py serwer Rocket można łatwo zainstalować, uruchomiać i zatrzymywać jako usługę Windows. Podobnie jest ze schedulerem web2py.

usługi Windows z nssm

Zamiast utrzymywać w web2py.py kod usługi Windows, wspierane jest zewnętrzne narzędzie opakowujące 'nssm'.

Biblioteka nssm jest uznanym narzędziem z dobrymi możliwościami, takimi jak automatyczny restart usług. Jego stosowanie oznacza również spójny sposób uruchamiania usług web2py, usług schedulera i czyszczenia procesów, takiego jak usuwanie sesji. Użycie opcji -W w poprzednio rozpatrywanym poleceniu jest nadal obsługiwane. Metoda nssm nie używa pliku options.py starej metody. Zamiast tego opcje są przekazywane w poleceniu linii poleceń (trochę przykładów znajduje się poniżej)

Recepta nssm: uruchamianie schedulera w nssm jako usługi Windows
usługa schedulera w Windows

Uruchamianie schedulera jako usługi Windows przynosi dużo korzyści. Najprościej jest pobrać biblioteke nssm (z htp://www.nssm.cc). Biblioteka nssm jest otwarto-źródłowym helpere harmonogramowania. Opakowuje on polecenia wykonywalne, aby włączyć je do usługi. Poleceniem uruchamiającym scheduler jest pythonw.exe -K <appname> Używamy nssm do opakowania właściwej usługi. Przedtem należy wybrać nazwę dla swojej usługi. Tworzenie odrębnej usługi dla poszczególnych aplikacji, wymagających użycia schedulera, ma poważne zalety. Konwencją nazewniczą dla usług może być taka jak web2py_scheduler_app1.

Po rozpakowaniu pakietu zip biblioteki nssm, w folderze zawierajacym wersję dla swojej architektury (32-bit lub 64-bit) otwórz okno poleceń Windows i wpisz:

nssm install web2py_scheduler_app1

Wyświetli to okno dialogowe z prośbą o wprowadzenie aplikacji i opcji. Aplikacją jest wykonywalny plik pythonw.exe z instalacji Pythona. Opcje, to pozostała część polecenia. Być może trzeba tu podać pełną ścieżkę do pliku skryptu web2py.py. Na przykład, wartością pola Options w oknie dialogowym nssm może być:

c:\web2py\web2py.py -K app1

gdzie app1 jest nazwą aplikacji.

Możliwe jest wywołanie schedulera z wielu aplikacji. Jednak w tym trybie web2py przydziela każdy scheduler do odrębnego podprocesu. Dlatego proces rozpoczęty przez usługę nie zostanie zniszczony, jeśli jedna instancja schedulera spowoduje problemy. W takim przypadku nie można skorzystać z automatycznego restartu usługi. Gdy stosuje się odrębną usługę dla każdej aplikacji, takiego problemu nie ma.

Recepta nssm: uruchamianie web2py.py jako usługi

Powyższy przykład pokazał nam jak stosować nssm. W celu uruchomienia web2py w trybie SSL na porcie 8041 i dołączenia kilku opcji, można w polu Options okna dialogowego nssm wprowadzić taki kod:

c:\web2py.py -p 8041 -i "0.0.0.0" --password="112233" --folder="d:\web2py_internet" --socket-timeout=10 --timeout=120 -c "d:\web2py_internet\applications\example.com.au.crt" -k "d:\web2py_internet\applications\web2py.key

Proszę mieć na uwadze, że nie jest to dobrym sposobem na przechowywania hasła, ponieważ menadżer zadań, pokazujący linię poleceń będzie ujawniał hasło. Rozeznaj opcję -a "<recycle>" linii poleceń.

Zabezpieczenie sesji a aplikacje administracyjne

bezpieczeństwo
aplikacje administracyjne

Udostępnianie publiczne aplikacji admin i kontrolerów appadmin jest bardzo niebezpieczne, chyba że dostęp do nich następuje przez HTTPS. Ponadto hasła i poświadczenia nie powinny być nigdy przesyłane niezaszyfrowane. Dotyczy to wszystkich aplikacji, nie tylko web2py.

Jeśli aplikacje wymagaja uwierzytelniania, trzeba wprowadzić w nich zabezpieczenie ciasteczek sesji przez:

session.secure()

Łatwym sposobem na skonfigurowanie bezpiecznego środowiska produkcyjnego na serwerze jest po pierwsze, zatrzymanie web2py i usunięcie wszystkich plików parameters_*.py z folderu instalacyjnego web2py. Następnie trzeba uruchomić web2py bez hasła. To całkowicie wyłączy aplikacje admin i appadmin:

nohup python web2py --nogui -p 8001 -i 127.0.0.1 -a '' &

Następnie trzeba uruchomić drugą instancję web2py dostępna tylko na localhost:

nohup python web2py --nogui -p 8002 -i 127.0.0.1 -a '<ask>' &

i utworzyć tunel SSH na lokalnym komputerze (z którego chce się uzyskać dostęp do interfejsu administracyjnego) do serwera (na którym jest uruchomiony web2py, example.com), stosując:

ssh -L 8002:127.0.0.1:8002 username@example.com

Teraz można uzyskać dostęp do interfejsu administracyjnego lokalnie poprzez przeglądarkę internetową na localhost:8002.

Ta konfiguracja jest bezpieczna, ponieważ interfejs administracyjny nie jest dostępny, gdy tunel jest zamknięty (użytkownik jest wylogowany).

To rozwiązanie jest bezpieczne na współdzielonym hoście tylko wtedy, gdy inni użytkownicy nie mają prawa odczytu do folderu, który zawiera web2py. W przeciwnym razie mogą oni wykraść ciasteczka sesji na serwerze.

Wydajność i skalowalność

skalowalność

Platworma web2py jest zaprojektowana tak, aby być łatwą do wdrożenia i skonfigurowania. Nie oznacza to pogorszenia wydajności i skalowalności, ale oznacza, że być może trzeba dostosować ją do potrzebnej skali.

W tym rozdziale rozpatrujemy wieloserwerową instalację web2py za serwerem NAT, który dostarcza lokalnie równoważenie obciążenia (ang. load-balancing).

W takim przypadku web2py działa od razum jeśli spełnione są pewne warunki. W szczególności, wszystkie instancje każdej aplikacji web2py muszą mieć dostęp do tego samego serwera bazy danych i muszą widzieć te same pliki. Ten ostatni warunek można zrealizować przez uczynienie niżej wymienionych folderów foldderami współdzielonymi:

applications/myapp/sessions
applications/myapp/errors
applications/myapp/uploads
applications/myapp/cache

Współdzielone foldery muszą obsługiwać blokowanie plików. Możliwymi rozwiązaniami sa ZFS (ZFS został opracowany przez Sun Microsystems i jest najlepszym wyborem), NFS (w NFS być może trzeba uruchomić demona nlockmgr dla umożliwienia blokowania plików) lub Samba (SMB).

Możliwe jest współdzielenie całego folderu web2py lub całefo folderu aplikacji, ale nie jest to dobry pomysł, ponieważ może spowodować niepotrzebne zwiększenie wykorzystania pasma sieciowego.

Omówiona powyżej konfiguracja, naszym zdaniem, powinna być badzo skalowalna, ponieważ redukuje ładowanie bazy danych prez przeniesienie obciążenia na współdzielony system plików, którego zasoby, choć wspólne, nie wymagają bezpieczeństwa transakcyjnego (tylko jeden klient w danym czasie ma dostęp do pliku sesji, pamięć podręczna zawsze wymaga blokady globalnej, przesyłana zawartość i komunikaty błędów są zapisywane raz i odczytywane z wielu plików).

Najlepiej, aby baza danych i współdzielony magazyn znajdowały się na dyskach RAID. Trzeba unikać błędu polegajacego na umieszczeniu bazy danych i współdzielonych folderów w tej samej przestrzeni magazynowej lub stworzenia tam nowego wąskiego gardła.

W każdym przypadku, trzeba dodatkowo wykonać optymalizację, co omawiamy poniżej. W szczególności zastanowimy się, jak pozbyć się po kolei tych współdzielonych folderów i jak zamiast tego przechowywac powiązane dane w bazie danych. Chociaż jest to możliwe, to nie zawsze jest to dobre rozwiązanie. Jednak mogą istnieć powodów, aby to zrobić. Jednym z nich jest fakt, że nie zawsze ma się możliwość skonfigurowania współdzielonych folderów.

Sztuczki zwiększające wydajność

W web2py kod aplikacji jest wykonywany za każdym żądaniem, tak więc można pokusić się o zminimalizowanie ilość wykonywanego kodu. Oto jak można to zrobić:

  • Uruchom raz migrate=True, następnie ustaw wszystkie tabele na migrate=False.
  • Skompiluj kod bajtowy aplikacji używając admin.
  • Użyj cache.ram tyle razy ile można, ale upewnij się, że użyto skończony zestaw kluczy, inaczej ilość używanej pamięci będzie gwałtownie rosła.
  • Zminimalizuj kod w modelach: nie definiuj w nich funkcji, definiuj je w kontrolerach, które tego wymagają, albo (jeszcze lepiej) definiuj funkcje w modelach, zaimportuj je i następnie wykorzystaj te funkcje według potrzeb.
  • Nie umieszczaj wielu funkcji w tym samym kontrolerze, ale stosuj wiele kontrolerów z kilkoma funkcjami.
  • Wywołaj session.forget(response) we wszystkich kontrolerach lub ewentualnie w funkcjach, które nie zmieniają sesji.
  • Staraj się unikać crona web2py i zamiast tego używaj procesów w tle. Cron web2py może uruchamiać zbyt wiele instancji Pythona i powodować nadmierne zużycie pamięci.

Sesje w bazie danych

Możliwe jest poinstruowanie web2py, aby przechowywał sesje w bazie danych a nie w folderze sessions. Trzeba to zrobić odrębie dla każdej aplikacji web2py, choć wszystkie one mogą wykorzystywać do przechowywania sesji tą samą bazę danych.

Biorąc pod uwagę połączenie z bazą danych

db = DAL(...)

można przechowywać sesję w bazie danych (db) umieszczając poniższe wyrażenie w pliku modelu w którym nawiązane jest połączenie:

session.connect(request, response, db)

Jeśli nie istnieje właściwa tabela bazy danych, web2py tworzy "pod maską" w bazie danych tabelę o nazwie web2py_session_appname zawierającą następujace pola:

Field('locked', 'boolean', default=False),
Field('client_ip'),
Field('created_datetime', 'datetime', default=request.now),
Field('modified_datetime', 'datetime'),
Field('unique_key'),
Field('session_data', 'text')

Pole "unique_key" przeznaczone jest na przechowywanie klucza uuid używanego do identyfikacji sesji w ciasteczku, natomiast "session_data" to speklowane (w cPickle) dane sesji.

Dla zminimalizowania dostępu do bazy danych należy unikać przechowywania sesji gdy nie jest to potrzebne, stosując:

session.forget()

Sesje, które są automatycznie zapominane, pozostaja bez zmian.

Przy stosowaniu zapisu sesji w bazie danych, folder "sessions" nie musi być udostępniany, ponieważ nie jest już wykorzystywany.

Proszę zauważyć, że jeśli sesje są wyłączone, nie trzeba przekazywać session do form.accepts i nie można używać session.flash ani CRUD.

HAProxy - wysokiej dostępności load balancer

HAProxy

Jeśli zajdzie potrzeba uruchamiania wielu procesów na wielu komputerach, zamiast przechowywania sesji w bazie danych lub w pamięci podręcznej, można zastosowania load balancer z przyłączanymi sesjami.

Pound[pound] i HAProxy[haproxy], to dwa load balancery HTTP i odwrotne serwery pośredniczący (ang. reverse proxy), które dostarczają przyłaczanych sesji (ang. sticky sessions). Tutaj omawiamy to drugie rozwiązanie, ponieważ jest ono bardziej powszechne na komercyjnych hostingach VPS.

Przez "przyłaczaną sesję" rozumiemy tu ciasteczko sesji, które zostało wydane tak, aby load balancer zawsze trasował żądania od klienta związanego z sesją na tym samym serwerze. Pozwala to na zapisanie sesji w loklanym systemie plików bez konieczności współdzielenia systemu plików.

W celu wykorzystania HAProxy trzeba wykonać niżej opisane czynności.

Po pierwszen zainstaluj pakiet haproxy na wyznaczonym komputerze Ubuntu:

sudo apt-get -y install haproxy

Po drugie, edytuj plik konfiguracyjny "/etc/haproxy.cfg" w celu wykonania czegoś takiego:

## this config needs haproxy-1.1.28 or haproxy-1.2.1

global
      log 127.0.0.1   local0
      maxconn 1024
      daemon

defaults
      log     global
      mode    http
      option  httplog
      option  httpchk
      option  httpclose
      retries 3
      option redispatch
      contimeout      5000
      clitimeout      50000
      srvtimeout      50000

listen 0.0.0.0:80
      balance url_param WEB2PYSTICKY
      balance roundrobin
      server  L1_1 10.211.55.1:7003  check
      server  L1_2 10.211.55.2:7004  check
      server  L1_3 10.211.55.3:7004  check
      appsession WEB2PYSTICKY len 52 timeout 1h

Dyrektywa listen instruuje HAProxy, na którym porcie ma oczekiwać połączenia. Dyrektywa server wskazuje gdzie HAProxy może znaleźć serwery pośredniczące. Dyrektywa appsession odpowiada za przyłaczaną sesję i wykorzystuje w tym celu ciasteczko WEB2PYSTICKY.

Po trzecie, włącz ten plik konfiguracyjny i urucho, HAProxy:

/etc/init.d/haproxy restart

Instrukcję konfiguracyjną dla Pound można znaleźć pod adresem URL:

http://web2pyslices.com/main/slices/take_slice/33

Czyszczenie sesji

Trzeba mieć świadomość, że w środowisku produkcyjnym sesje piętrzą się dość szybko. W web2py istnieje skrypt o nazwie:

scripts/sessions2trash.py

który, gdy działa w tle, okresowo usuwa wszystkie sesje, które nie są dostępne przez określony okres czasu. Skrypt ten działa zarówno dla sesji przechowywanych w systemie plików jak i w bazie danych.

Oto kilka typowych przypadków użycia:

  • Usuwanie wygasłych sesji co 5 minut:
nohup python web2py.py -S app -M -R scripts/sessions2trash.py &

W Windows trzeba uzyć nssm, tak jak opisano to w poprzednim rozdziale dotyczącym schedulera. Prawdopodobnie trzeba będzie wtedy dołączyć pełna ścieżkę do web2py.py, jak i foldera skryptów a końcowy znak et (&) nie jest konieczny.

  • Usuniecie sesji starszych od 60 minut, niezależnie od czasu wygasania, wyjściem opisowym i zakończenie skryptu:
python web2py.py -S app -M -R scripts/sessions2trash.py -A -o -x 3600 -f -v
  • Usuniecie wszystkich sesji, bez względu na czas wygaśnięcia i zakończenie skryptu:
python web2py.py -S app -M -R scripts/sessions2trash.py -A -o -x 0

Tutaj app jest nazwą aplikacji.

Przechowywanie przesłanych plików w bazie danych

Domyślnie wszystkie przesyłane na serwer pliki, obsługiwane przez formularze SQLFORM podlegają bezpiecznej zmianie nazwy i zapisaniu w systemie plików w folderze "uploads". Możliwe jest poinstruowanie web2py, aby zamiast tego przechowywał przesłane pliki w bazie danych.

Przyjmijmy teraz, że mamy następyjącą tabelę:

db.define_table('dog',
    Field('name')
    Field('image', 'upload'))

gdzie pole dog.image jest typu upload. W celu spowodowania, aby przesłany obraz został zapisany w tym samym rekordzie, co nazwa psa, trzeba zmodyfikować definicję tabeli przez dodanie pola typu blob i połączenie go z polem upload:

db.define_table('dog',
    Field('name')
    Field('image', 'upload', uploadfield='image_data'),
    Field('image_data', 'blob'))

Tutaj "image_data" reprezentuje po prostu dowolną nazwą dla nowego pola blob.

Linia 3 instruuje web2py, aby jak zwykle bezpiecznie zmienił nazwę obrazu, przechował nową nazwę w polu "image" a dane obrazu w polu o nazwie "image_data", zamiast zapisywać je w systemie plików. Wszystko to jest wykonywane automatycznie przez formularz SQLFORM i żaden dodatkowy kod nie jest potrzebny.

W tym rozwiazaniu folder "uploads" jest zbyteczny.

Na Google App Engine pliki są przechowywane domyślnie w bazie danych, bez konieczności definiowania pola blob, ponieważ jest ono tworzone automatycznie.

Gromadzenie biletów

Domyślnie web2py przechowuje bilety (dla komunikatów błędów) w lokalnym systemie plików. Nie ma sensu przechowywać biletów bezposrednio w bazie danych, ponieważ wiekszość błędów w środowisku produkcyjnym, to błędy bazy danych.

Przechowywanie biletów nigdy nie stanowi wąskiego gardła, ponieważ zazwyczaj następuje rzadko. Dlatego w środowisku produkcyjnym z wieloma współbieżnymi serwerami jest wystarczające przechowywanie biletów we współdzielonym folderze. Niemniej jednak, ponieważ tylko administrator potrzebuje mieć dostęp do biletów, dobrym rozwiązaniem jest przechowywanie biletów w lokalnym nie współdzielonym folderze "errors" i okresowe gromadzenie ich i ewentualnie czyszczenie tego folderu.

Jedną z możliwości jest okresowe przenoszenie wszystkich lokalnych biletów do bazy danych.

W tym celu w web2py istnieje skrypt:

scripts/tickets2db.py

Domyślnie skrypt ten pobiera URI bazy danych z pliku ticket_storage.txt zapisanego w prywatnym folderze. Plik ten powinien zawierać łańcuch tekstowy, który jest przekazywany bezpośrednio do instancji DAL, podobnie do tego:

mysql://username:password@localhost/test
postgres://username:password@localhost/test
...

Pozwala to pozostawić ten skrypt takim jaki jest: jeśli ma się wiele aplikacji, będzie on dynamicznie wybierał właściwe połączenie dla każdej aplikacji. Gdy chce się ustawić sztywny adres URI, trzeba zmienic drugie odniesienie na db_string, zaraz po linii except. Skrypt ten można uruchomić poleceniem:

nohup python web2py.py -S myapp -M -R scripts/tickets2db.py &

gdzie myapp jest nazwą aplikacji.

Skrypt ten działa w tle i przenosi wszystkie bilety do bazy danych co 5 minut i usuwa bilety lokalne. Później można przegladać komunikaty błędów wykorzystując aplikację administracyjną, klikając na przycisk "switch to: db" button na górze okna, mając możliwość korzystania z tych samych możliwości, jak w przypadku przechowywania biletów w systemie plików.

W tym rozwiązaniu folder "errors" nie wymaga udostępniania, ponieważ komunikaty błędów będą przechowywane w bazie danych.

Memcache

Memcache

Pokazaliśmy, że web2py udostępnia dwa rodzaje pamięci podręcznej: cache.ram and cache.disk. Wprawdzie obydwa działają w środowisku z wieloma współbieżnymi serwerami, ale nie działają zgodnie z oczekiwaniami. W szczególności cache.ram będzie buforował dane tylko na poziomie serwera, stając się w ten sposób bezużytecznym. Pamięć cache.disk będzie również buforować dane na poziomie serwera, chyba że folder "cache" jest folderem współdzielonym, który obsługuje blokowanie - tak więc zamiast przyśpieszać działanie systemu, staje się wąskim gardłem.

Jednym z rozwiązań jest zastosowanie Memcache. W web2py dostępne jest API Memcache.

W celu zastosowania Memcache, utwórz nowy plik modelu, na przykład 0_memcache.py i dodaj tam następujący kod:

from gluon.contrib.memcache import MemcacheClient
memcache_servers = ['127.0.0.1:11211']
cache.memcache = MemcacheClient(request, memcache_servers)
cache.ram = cache.disk = cache.memcache

Pierwsza linia importuj Memcache. Druga linia jest lista gniazd Memcache (serwer:port). Trzecia linia definiuje cache.memcache. Czwarta linia redefiniowuje cache.ram i cache.disk w zakresie Memcache.

Można wybrać redefiniowanie tylko jednego obiektu pamięci, definiując zupełnie nowy obiekt wskazujący na obiekt Memcache.

W tym rozwiązaniu folder "cache" nie musi byc współdzielony, ponieważ nie będzie juz dostępny.

Kod ten wymaga posiadania serwerów Memcache w swojej lokalnej sieci. Proszę skonsultować to zagadnienie z dokumentacją Memcache w celu uzyskania informacji o konfiguracji tych serwerów.

Sesje w Memcache

Jeśli potrzebuje się sesji i nie chce się używać load balancera z przyłączanymi sesjami, można przechowywać sesje w Memcache:

from gluon.contrib.memdb import MEMDB
session.connect(request,response,db=MEMDB(cache.memcache))

Buforowanie przy użyciu Redis

Redis

Alternatywnie zamiast Memcache można zastosować Redis[Redis].

Zakładając, że mamy już zainstalowany Redis i że jest on uruchomiony na localhost na porcie 6379, możemy połączyć sie z nim, używając następujacy kod umieszczony w modelu:

from gluon.contrib.redis_cache import RedisCache
cache.redis = RedisCache('localhost:6379',db=None, debug=True)

gdzie localhost:6379 jest łańcuchem połączenia a db nie jest obiektem DAL, ale nazwą bazy danych Redis.

możemy teraz uzyć cache.redis w miejsce (lub wraz z) cache.ram i cache.disk.

Możemy również uzyskać statystyki Redis wywołując:

cache.redis.stats()

Podsystem pamięci podręcznej Redis pozwala uniknąć niesławnego problemu "thundering herd" (problemu olbrzymiego stada). Funkcjonalność ta nie jest domyślnie aktywna, ponieważ zazwyczaj wybiera się Redis dla szybkości, ale przy niewielkim koszcie można upewnić się, że w danym momencie tylko jeden wątek (proces) może ustawić wartość. Do aktywowania tej możliwości wystarczy przekazać do wywołania RedisCache parametr with_lock=True. Można również włączyć tryb "na żądanie" poprzez wyrażenie:

value = cache.redis('mykey', lambda: time.time(), with_lock=True)

Sesje w Redis

Jeśli Redis jest już w stosie RAM, to czemu go nie wykorzystać do przechowywania sesji?

from gluon.contrib.redis_session import RedisSession
sessiondb = RedisSession('localhost:6379',db=0, session_expiry=False)
session.connect(request, response, db = sessiondb)

Kod ten został przetestowany z sesjami o wielkości ~1MB. Dopóki Redis mieści się w pamięci, czas potrzebny do obsługi jednej sesji lub 1MB danych jest taki sam, podczas gdy przy sesjach zapisywanych w plikach lub w bazie danych różnica jest niezauważalna dla ~40KB danych, lecz powyżej tej bariery poprawa szybkości na korzyść Redis rośnie bardzo dużo. Dużą poprawę można również zauważyć, gdy ma się uruchomioną "farmę" instancji web2py, ponieważ współdzieląc folder sesji lub mając wiele połączonych procesów do bazy danych często przeciąża się system.

W Redis trzeba będzie mieć 1 klucz na sesję, plus 2 klucze - jeden na trzymanie liczby całkowitej (potrzebnej dla przypisania różnych kluczy sesji) a drugi na trzymanie zestawu wszystkich wygenerowanych sesji, tak więc na 1000 sesji potrzeba 1002 klucze.

Jeśli nie jest ustawiony parametr session_expiry, sesje bedą obsługiwane jak zwykle - zachodzi konieczność czyszczenia sesji co pewien czas.

Jednak gdy ustawi się session_expiry, sesje będą usuwane automatycznie po n sekundach, np. jeśli ustawi się 3600, sesja będzie wygasać dokładnie godzinę po ostatniej aktualizacji. Od czasu do czasu należy uruchomić sessions2trash.py tylko do czyszczenia klucza przechowującego zestaw wszystkich poprzednio wydanych sesji (czyszczenie ~1MB sesji wymaga 3 sekund). Zaplecze Redis dla sesji jest jedynym, które może zapobiec jednoczesnej modyfikacji tej samej sesji: jest to szczególnie ważne dla aplikacji intensywnie wykorzystujących Ajax, które intensywnie zapisują sesje w częściowo współbieżny sposób. Nie jest to domyślnie wymuszane ze względu na szybkość, jednak gdy chce się włączyć to zachowanie blokujące, wystarczy włączyć to w parametrze with_lock=True przekazywanym do obiektu RedisSession.

Usuwanie aplikacji

usuwanie aplikacji

W środowisku produkcyjnym najlepiej nie instalować domyślnych aplikacji: admin, examples i welcome. Chociaż te aplikacje są bardzo małe, to nie są konieczne.

Usuwanie tych aplikacji jest bardzo proste - wystarczy usunąć foldery z tymi aplikacjami.

Używanie replikowanych baz danych

W środowisku wysokiej wydajności można mieć architekturę bazy danych master-slave z wieloma replikowanymi bazami podrzędnymi i ewentualnie kilkoma replikowanymi serwerami. DAL może obsługiwać taką sytuację i warunkowo łączyć do różnych serwerów, w zależności od parametrów żądania. API które można do tego użyć jest opisane w rozdziale 6. Oto przykład:

from random import sample
db = DAL(sample(['mysql://...1','mysql://...2','mysql://...3'], 3))

W tym przypadku, różne żądania HTTP będą obsługiwane przez różne bazy danych w sposób losowy a każda baza danych będzie trafiana prawie z tym samym prawdopodobieństwem.

Można również zaimplementować prosty Round-Robin:

def fail_safe_round_robin(*uris):
     i = cache.ram('round-robin', lambda: 0, None)
     uris = uris[i:]+uris[:i] # rotate the list of uris
     cache.ram('round-robin', lambda: (i+1)%len(uris), 0)
     return uris
db = DAL(fail_safe_round_robin('mysql://...1','mysql://...2','mysql://...3'))

Jest to bezawaryjne w tym sensie, że jeśli serwer bazy danych przypisany do żądania nie uzyska połączenia, DAL będzie próbował połączyć się z następnym serwerem z kolei.

Możliwe jest również połączenie z różnymi bazami danych, w zależnosci od żądanej akcji lub kontrolera. W konfiguracji master-slave bazy danych niektóre akcje wykonywane są tylko dla odczytu i tylko niektóre osoby mają proawo zapisu i odczytu. Ten pierwszy tryb może bezpiecznie łączyć z bazą danych slave, natomiast drugi tryb powinien łączyć do bazy master. Tak więc można zrobić tak:

if request.function in read_only_actions:
   db = DAL(sample(['mysql://...1','mysql://...2','mysql://...3'], 3))
elif request.action in read_only_actions:
   db = DAL(shuffle(['mysql://...1','mysql://...2','mysql://...3']))
else:
   db = DAL(sample(['mysql://...3','mysql://...4','mysql://...5'], 3))

gdzie 1,2,3 są bazami slave a 3,4,5 bazami masters.

Kompresowanie statycznych plików

Przeglądarki mogą dekompresować treść w locie, tak więc kompresowanie treści dla tych przeglądarek oszczędza przepustowość i obniża czas reakcji. Obecnie większość serwerów internetowych może kompresować treść w locie i wysyłać ją do przegladarek żądających skompresowanej treści. Jednak dla plików statycznych marnuje sie cykle CPU do kompresji w kółko tych samych treści.

Można wykorzystać skrypt scripts/zip_static_files.py do tworzenia skompresowanych wersji plików statycznych i serwować je bez marnowania czasu CPU. Uruchom w cronie:

python web2py.py -S myapp -R scripts/zip_static_files.py

Skrypt zajmuje sie stworzeniem (lub zaktualizowaniem) skompresowanej wersji plików i zapisuje je dodajac do ich nazwy rozszerzenie .gz. Trzeba tylko umożliwić, aby nasz serwer internetowy wykorzystał przy przesyłaniu tych plików moduł [apache-content-negotiation] albo [nginx-gzipstatic].

Wdrożenie web2py na PythonAnywhere

PythonAnywhere

Aplikację web2py najprościej jest wdrożyć na PythonAnywhere[PythonAnywhere].

PythonAnywhere to programistyczne i hostingowe środowisko, które jest wyświetlane w przeglądarce internetowej i działa w chmurze serwerowej. Jest tam wszystko, co jest potrzebne do uruchomienia Pythona i wspiera ono specjalnie platformę web2py. Z naszego doświadczenia wynika, że PythonAnywhere jest łatwy w użyciu, szybki i silny. Zapewnia również dostęp do baz danych MySQL, powłok Pythona i integrację z Dropbox. Dostępny jest też profesjonalny hosting, jeśli darmowe konto podstawowe jest niewystarczające.

W celu użycia PythonAnywhere utwórz konto, zaloguj się a następnie wykorzystaj dostarczony panel do dodania nowej aplikacji internetowej typu web2py.

image

image

Ten interfejs administracyjny poprosi również o hasło administracyjne.

image

Folder web2py zostanie utworzony w folderze użytkownika.

Alternatywnie, możesz również użyć internetowej powłoki BASH, aby zainstalować web2py, tak jak się to normalnie robi:

wget http://www.web2py.com/examples/static/web2py_src.zip
unzip web2py_src.zip

W obu przypadkach musisz utworzyć hasło administracyjne z poziomu powłoki, w celu jego późniejszego wykorzystania:

python -c "from gluon.main import save_password; save_password(raw_input('admin  password: '),433)"

Następnie odwiedź panel "Web" wykorzystując interfejs internetowy i edytuj plik "/var/www/<username>_pythonanywhere_com_wsgi.py". Jest to punkt wejścia do programu (w naszym przypadku web2py) i jak się można domyśleć jest oparty na protokole WSGI.

Edytuj plik "/var/www/<username>_pythonanywhere_com_wsgi.py" i napisz w nim:

import sys
path = '/home/<username>/web2py'
if path not in sys.path: sys.path.append(path)
from wsgihandler import application # the web2py handler

Tutaj "<username>" jest nazwą użytkownika w PythonAnywhere.

Po zainstalowaniu web2py zauważysz, że nie ma potrzeby uruchamiać lub konfigurować serwera internetowego. PythonAnywhere zapewnia od razu dostęp do serwera internetowego i przeładowuje automatycznie po edycji pliku konfiguracyjnego lub po wciśnięciu przycisku "Reload web app" na pulpicie. Kazdy może natychmiast usyskać dostęp do aplikacji po wprowadzeniu adresu URL:

http://yourusername.pythonanywhere.com/

Zapewniona jest też bezpieczna wersja witryny internetowej i jest ona wymuszana, jeśli używa sie interfejsu administracyjnego web2py:

https://yourusername.pythonanywhere.com/admin/default/index

Dziękujemy zespołowi PythonAnywhere za pomoc i wsparcie web2py.

Wdrożenie web2py na Heroku

Heroku

Heroku[heroku] jest nowoczesnym i sprawnym wieloplatformowym rozwiązaniem hostingowym. Pozwala na wypchnięcie aplikacji do chmury serwerowej przy użyciu Git. W celu korzystania z Heroku trzeba mieć zainstalowany Git i SDK Heroku. Interakcja z Heroku wykorzystuje lokalny pakiet SDK i wprowadzane polecenia są wypychane na serwer i tam wykonywane.

Aplikacje działające na Heroku nie mogą opierać sie na trwałym systemie plików, ponieważ są okresowo odświeżane. Z tego powodu w systemie plików może być przechowywany tylko kod aplikacji. Wszystkie dane muszą być przechowywane w bazie danych. Heroku wykorzystuje PostgreSQL. Jednak PostgreSQL jest również możłiwy do skonfigurowania przy użyciu SDK Heroku a URI bazy danych jest przydzielany dynamicznie w czasie wykonywania i przechowywany w zmiennej środowiskowej.

Oznacza to, że aplikacje web2py muszą zostać zmodyfikowane do pracy na Heroku, tak aby wykorzystywały bazę danych a nie system plików.

W celu pomocy, web2py zawiera skrypt "heroku.sh". Wszystko co trzeba zrobić, to zamienić:

db = DAL(...)

w kodzie aplikacji na:

from gluon.contrib.heroku import get_db
db = get_db(name=None, pool_size=10)

Tutaj name jest zmienną środowiskową zawierająca identyfikator URI do PostgreSQL na Heroku (coś podobnego do HEROKU_POSTGRESQL_RED_URL). Jej domyślną wartością jest None i jeśli jest tylko jedna zmienna środowiskowa HEROKU_POSTGRESQL_*_URL, to zostanie użyta. Parametr pool_size jest wielkością puli DAL.

Gdy baza danych PostgreSQL jest niedostępna na platformie Heroku, get_db będzie używać programistycznej bazy danych "sqlite://heroku.test.sqlite".

W obu przypadkach sesje są przechowywane w bazie danych.

W web2py zawarty jest skrypt "scripts/setup-web2py-heroku.sh" pozwalający wdrożyć instalację web2py na Heroku. Wykonuje on następujace czynności:

Instaluje środowisko "virtualenv" i sterownik "psycopg2":

sudo pip install virtualenv
sudo pip install psycopg2

Tworzy i aktywuje "virtualenv":

virtualenv venv --distribute
source venv/bin/activate

Następnie tworzy plik wymagań:

pip freeze > requirements.txt

oraz tworzy "Procfile", które powiadamia Heroku jak uruchamiać web2py:

echo "web: python web2py.py -a 'yourpassword' -i 0.0.0.0 -p $PORT" > Procfile

Można zmienić tą linię, jeśli chce się korzystać z innego serwera. Trzeba ją edytować, aby ustawić tam własne hasło administracyjne. Zmienna $PORT jest tutaj poprzedzona znakiem ucieczki, ponieważ jej wartość jest ustalana w czasie wykonania. Trzeba również rozważyć uruchamianie web2py z "gunicorn" stosując anyserver.py, ponieważ jest to jeden z serwerów internetowych zalecanych dla Pythona.

Wreszcie skrypt tworzy repozytorium Git:

git init
git add .
git add Procfile
git commit -a -m "first commit"

wypychając wszystko do Heroku i uruchamiając aplikację:

heroku create
git push heroku master
heroku addons:add heroku-postgresql:dev
heroku scale web=1
heroku open

Tutaj heroku jest częścią polecenia konsolowego SDK Heroku.

Dziękujemy Craigowi Krestiensowi z Heroku za pomoc w napisaniu tej recepty.

Wdrożenie web2py na EC2

Amazon EC2

Amazon Elastic Compute Cloud (Amazon EC2) to usługa internetowa oferująca dostęp do chmury obliczeniowej o zmiennym rozmiarze mocy obliczeniowej. Jest to jedna z najwiekszych i najbardziej popularnych chmur. Wiele innych platform chmurowych jest uruchomionych na EC2. Na EC2 można uruchomić dowolną aplikację przez utworzenie i wdrożenie obrazu dysku. Amazon następnie dostarcza API do replikowania tego obrazu, podczas udostępniania części systemu plikow.

Opis całej procedury jest poza zakresem tej książki, ale przyjmując, że masz już konto na Amazon EC2, możesz użyć Turnkey Hub do znalezienia i wdrożenia gotowego obrazu web2py:

https://hub.turnkeylinux.org/amazon/launch/web2py/

Po wdrożeniu obrazu dysku, można się do niego logować jak do zwykłego VPS oraz można zarządzać nim (backup/restore/copy) poprzez interfejs internetowy Amazon EC2.

Wdrożenie web2py na Google App Engine

Google App Engine

Możliwe jest uruchomienie kodu web2py na Google App Engine (GAE)[gae] , właczając w to kod DAL.

GAE obsługuje dwie wersje Pythona: 2.5 and 2.7, które obsługiwane są też przez web2py, ale wersja 2.5 jest wersją domyślną (może to się zmienić w przyszłości). Proszę zaglądnąć do pliku "app.yaml" opisanym poniżej w celu poznania szczegółów konfiguracyjnych.

GAE obsługuje również bazę danych Google SQL (zgodną z MySQL) i Google NoSQL (zwaną "Datastore").

W web2py obsługiwane są obydwie te bazy danych i w rzeczywistości może łączyć się z obydwoma bazami w tym samym czasie, używając łańcuchów połączeń opisanych w rozdziale 6.

Platforma GAE oferuje szereg udogodnień w stosunku do normalnych rozwiązań hostingowych:

  • Łatwość wdrożenia. Google całkowicie uabstrakcyjniają swoją architekturę.
  • Skalowalność. Google replikują aplikację tyle razy ile potrzeba, aby obsłużyć wszystkie jednoczesne żądania.
  • Można wybierać pomiędzy bazami danych SQL i NoSQL (albo korzystać z obydwu).

Lecz są również słabe strony:

  • Brak dostępu do odczytu lub zapisu systemu plików.
  • Brak komunikacji HTTPS, chyba że korzysta się z domeny appspot.com z certyfikatem Google.
  • Obsługiwane są nie wszystkie biblioteki Pythona (scipy jest tego przykładem w chwili pisania).

Gdy Google Cloud SQL jest zwykłą bazą danych MySQL, to Google Datastore ma kilka istotnych wad:

  • Brak typowych transakcji - spójność końcowa a nie spójność silna dla zapytań.
  • Brak złożonych zapytań Datastore. W szczególności brak jest operatorów JOIN, LIKE i DATE/DATETIME.
  • Brak możliwości wielu podzapytań OR, o ile nie wiążą sie one z tym samym polem.

Tutaj dokonamy szybkiego przeglądu GAE i skupimy się na określonych problemach związanych z web2py, dla poznania szczegółów odsyłamy do oficjalnej dokumentacji GAE.

UWAGA: Trzeba uruchomić web2py z dystrybucji źródłowej a nie binarnej.

Konfiguracja

Są trzy pliki konfiguracyjne, które trzeba mieć na uwadze:

web2py/app.yaml
web2py/queue.yaml
web2py/index.yaml

Pliki app.yaml i queue.yaml najłatwiej utworzyć za pomocą szablonów app.example.yaml i queue.example.yaml jako punktu wyjścia. Plik index.yaml jest tworzony automatycznie przez program wdrożeniowy Google.

Plik app.yaml ma następującą strukturę (którą skrócono tutaj, stosując znak ...):

application: web2py
version: 1
api_version: 1
runtime: python
handlers:
- url: /_ah/stats.*
  ...
- url: /(?P<a>.+?)/static/(?P<b>.+)
  ...
- url: /_ah/admin/.*
  ...
- url: /_ah/queue/default
  ...
- url: .*
  ...
skip_files:
...

Plik app.example.yaml (po skopiowaniu do app.yaml) zostaje skonfigurowany dla wdrożeniowej aplikacji welcome web2py, ale nie dla aplikacji admin lub example. Trzeba zamienić web2py na identyfikator swojej aplikacji, który zastosowało się podczas rejestracji na Google App Engine.

Wyrażenie url: /(.+?)/static/(.+) instruuje GAE, aby obsługiwał bezpośrednio pliki statyczne aplikacji, bez wywoływania logiki web2py, dla prędkości.

Wyrażenie url:.* instruuje web2py, aby używał skrypt gaehandler.py dla każdego innego żądania.

W skip_files: session jest listą wyrażeń regularnych dla plików, których nie trzeba wdrażać na GAE. W szczególności linie:

 (applications/(admin|examples)/.*)|
 ((admin|examples|welcome).(w2p|tar))|

powiadamiają GAE, aby nie wdrażał domyślnych aplikacji, z wyjatkiem rozpakowanej aplikacji szkieletowej welcome. Można tutaj dodać więcej aplikacji, które mają byc ignorowane.

Przypuszczalnie nie trzeba wprowadzać żadnych innych zmian w app.yamlpoza identyfikatorem i wersją aplikacji, choć też można wykluczyć aplikację welcome.

Plik queue.yaml jest używany do konfigurowania kolejki zadań GAE.

Plik index.yaml jest generowany automatycznie podczas lokalnego uruchomienia aplikacji, za pomocą appserver GAE (serwer internetowy dostarczany w SDK Google). Plik ten zawiera coś podobnego do:

indexes:
- kind: person
  properties:
  - name: name
    direction: desc

W tym przykładzie GAE jest informowany, aby utworzył indeks dla tabeli "person", która będzie używana do sortowania wg. pola "name" w kolejnosci alfabetycznej. Bez odpowiednich indeksów nie będzie można wyszukiwać i sortować rekordy.

Ważne jest, aby zawsze uruchamiać lokalnie swoje aplikace na sewerze appserver i przed wdrożeniem wypróbować każdą funkcjonalność. będzie to miało znaczenie nie tylko dla celów testowych, ale też dla automatycznego wygenerowania pliku "index.yaml". Od czasu do czasu można edytować ten plik i wykonać czyszczenie, takie jak usuwanie powielonych wpisów.

Uruchamianie i wdrożenie

Linux

Zakładamy, że masz już zainstalowany SDK GAE. W czasie pisania tego artykułu, GAE obsługiwał Python 2.5.2. Możesz uruchomić swoja aplikację z poziomu folderu "web2py" wykorzystując polecenie appserver:

python2.5 dev_appserver.py ../web2py

Uruchomi to appserver i będzie można uruchomić aplikacje pod adresem URL:

http://127.0.0.1:8080/

W celu przesłania aplikacji na GAE, upewnij się, że masz wypełniony plik "app.yaml", tak jak wyjaśniono to poprzednio i ustaw odpowiedni identyfikator aplikacji. Następnie uruchom:

python2.5 appcfg.py update ../web2py
Mac, Windows

Na Mac i Windows używa się Google App Engine Launcher. Oprogramowanie to pobiera sie ze strony ref.[gae] .

Wybierz [File][Add Existing Application], ustaw path na ścieżkę do folderu głównego web2py i wciśnij przycisk [Run] na pasku narzędziowym. Po lokalnym przetestowaniu aplikacji można zabrać się za jej wdrożenie na GAE, przez proste kliknięcie przycisku [Deploy] na pasku narzędziowym (zakładamy tu, że masz już konto na GAE).

image

Na GAE bilety (komunikaty błędów) są rejestrowane na konsoli administracyjnej, gdzie rejestry mogą być dostępne i możliwe do wyszukania online.

image

Konfigurowanie handlera

Plik gaehandler.py jest odpowiedzialny za serwowanie plików na GAE i ma kilka opcji. Oto ich wartości domyślne:

LOG_STATS = False
APPSTATS = True
DEBUG = False

LOG_STATS będzie rejestrował w dziennikach GAE czas sewrwowania stron.

APPSTATS umożliwi, aby appstats GAE dostarczał profilowane statystyki. Zostana one udostępnione pod adresem URL:

http://localhost:8080/_ah/stats

DEBUG ustawia tryb debugowania. W tym trybie nie ma praktycznej różnicy w działaniu, chyba że dokona się jawnego zaznaczenia w kodzie poprzez gluon.settings.web2py_runtime.

Unikanie systemu plików

Na GAE nie ma się dostępu do systemu plików. Nie można otworzyć żadnego pliku do zapisu.

Wszystkie przesyłane pliki web2py automatycznie zapisuje w magazynie danych, niezależnie od tego, czy pole (pola) "upload" mają atrybut uploadfield attribute, czy też nie.

W bazie danych powinno się przechowywać też sesje i bilety i trzeba do robić jawnie:

if request.env.web2py_runtime_gae
    db = DAL('gae')
    session.connect(request,response,db)
else:
    db = DAL('sqlite://storage.sqlite')

powyższy kod sprawdza, czy uruchomiło się GAE, łączy z BigTable i instruuje web2py, aby przechował tam sesje i bilety. Jeśli brak jest połączenia z GAE następuje łączenie z bazą danych. Kod ten znajduje się już w aplikacji szkieletowej w pliku "db.py".

Memcache

Jeśli się tego chce, to sesje można przechowywać także w Memcache:

from gluon.contrib.gae_memcache import MemcacheClient
from gluon.contrib.memdb import MEMDB
cache.memcache = MemcacheClient(request)
cache.ram = cache.disk = cache.memcache
session.connect(request,response,db=MEMDB(cache.memcache.client))

Proszę zauważyć, że na GAE nie można używać cache.ram i cache.disk, więc wskazaliśmy je jako cache.memcache.

Kwestie związane z Datastore

Podczas gdy Google Clould SQL funkcjonuje jak zwykła baza danych SQL i jest oparty na MySQL (w chwili pisania tego artykułu), to Google Datastore znacznie się różni.

Spójność końcowa

UWAGA: Przez spójność końcową rozumie się tu tzw. model spójność BASE (ang. Basically Available Soft State with Eventual cosnsistency).

Oferowana przez Google Datastore spójność końcowa wymaga szczególnej uwagi. W Datastore transakcje, które opierają sie na podstawowych kluczach Datastore lub na współdzielonych grupach encji, oferują spójność silną: każda kolejna transakcja lub zapytanie będzie dawać ten sam wynik, co każda wcześniejsza transakcja dla tego samego klucza lub grupy encji. Z drugiej strony, zapytanie nie opierające się na kluczach podstawowych lun na współdzielonych pgrupach encji oferują tylko spójność końcową: dane nowe lub zmienione będą ostatecznie dostępne dla zapytań, po bliżej nieokreślonym czasie, który wynosi zazwyczaj kilka sekund.

W web2py nie uzywa się transakcji opartych na podstawowych kluczach Datastore, ani na współdzielonych grupach encjach. Wyrażenie:

id = db.table.insert(field1=value1, field2=value2)

daje następujący efekt:

  • Wstawiany jest nowy wiersz w tabeli db.table. Ten nowy wiersza ma pole id, którego wartość jest przypisywana przez web2py. Ma też podstawowy klucz Datastore, ale kluczem tym nie jest id i nie jest on udostępniony.
  • Aktualizowane sa indeksy na db.table. W szczegóności, aktualizowany jest indeks na polu id, tak więc nowy wiersz jest ostatecznie dostępny dla zapytań dla db.table.id.

Dopiero po tych dwóch czynnościach nowe dane stają sie dostęþne dla zapytań wykorzystujących pole id. W szczególności, poniższy wzorzec web2py niepowiedzie się:

def insert():
    form = SQLFORM(db.table)
    if form.process().accepted:
        session.flash = T('The data has been inserted.')
        redirect(URL('view', args=[form.vars.id]))
    return dict(form=form)

def view():
    row = db.table(request.args(0))
    if row is None:
        session.flash = T('Data not found')
        redirect(URL(index))
    form = SQLFORM(db.table, record=row, readonly=True)
    return dict(form=form)

Gdy użytkownik odwiedza stronę insert i wstawia dane, zostaje przekierowany do strony view. Podczas używania Google Datastore, często wstawiane dane nie zostają odnalezione przez kontroler view. Dlaczego?

Otóż, w kontrolerze insert dane są wstawiane do bazy danych a transakcja zostaje zakończona. Następnie, po zakończeniu transakcji, Google Datastore przebudowuje asynchronicznie indeksy, w tym indeks dla pola id w tabeli db.table. Gdy uzytkownik uzyskuje dostęp do kontrolera view controller, to nie ma gwarancji, że indeks dla pola db.table.id już obejmuje nowe dane i dlatego często napotyka na komunikat "Data not found".

Brak złaczeń JOIN

W Datastore jest brak operacji JOIN i typowych funkcjonalności relacyjnych, co wymaga przeniesienia złaczeń JOIN w zapytaniach web2py i denormalizacji bazy danych.

Google App Engine obsługuje kilka specjalnych typów pól, takie jak ListProperty i StringListProperty. W web2py można używać te typy stosujac następującą starą składnię:

from gluon.dal import gae
db.define_table('product',
    Field('name'),
    Field('tags', type=gae.StringListProperty())

lub równoważną nową składnię:

db.define_table('product',
    Field('name'),
    Field('tags', 'list:string')

W obu przypadkach pole "tags" jest typu StringListProperty, dlatego jego wartością musi być lista łańcuchów, zgodna z dokumentacją GAE. Pteferowana jest druga notacja, ponieważ w niej web2py traktuje te pole w bardziej inteligentny sposób w kontekście formularzy i dlatego, że będzie działać też w relacyjnych bazach danych.

Podobnie, web2py obsługuje typy list:integer i list:reference które są mapowane na ListProperty(int).

Typy list są omówione w rozdziale 6.

Migracja bazy danych

Dobra praktyka przy migracji wykorzytującej Google AppEngine jest następująca. AppEngine obsługuje wiele wersji kodu. Użyj jednej wersji kodu (np. wersji 1) dla strony widocznej przez użytkownika i innej wersji (np. wersji 2) dla kodu admininistracyjnego. W app.yaml dla wersji 2, trzeba zadeklarować handler jak niżej (przy załozeniu używania Python 2.7):

- url: .*
  script: gaehandler.wsgiapp    # WSGI (Python 2.7 only)
  secure: optional
  login: admin

Klauzula login: admin sprawia, że tylko administratorzy mogą korzystać z wersji 2. W ciagu połaczenia z bazą danych, trzeba określić migrate_enabled=False. W celu przeprowadzenia migracji najlepiej jest wyłaczyć jednoczesny dostęp do migracji. Należy postępować w następujący sposób:

  • Dodać plik o nazwie DISABLED do głównego katalogu aplikacji wersji 1 (nadrzędny katalog dla katalogów /controllers, /views itd.) i przesłać nową wersję do GAE. Wyłączy to wersję 1 i wyświetli komunikat "The site is temporarily down for maintenance".
  • W ciągu połączenia db w kodzie wersji dodać migrate_enabled=True i odwiedzić ten adres z poziomu konta administracyjnego, wyzwalajac migrację.
  • Dodać do kodu wersji 2 wyrażenie migrate_enabled=False w celu wyłączenia poprzedniej migracji.
  • Usunąć plik o nazwie DISABLED z wersji 1 i przesłać kod do wersji 1. Sprawi to, że witryna będzie znowu widoczna dla wszystkich.

GAE a HTTPS

Jeśli aplikacja ma identyfikator "myapp", to domena w GAE ma adres:

http://myapp.appspot.com/

i może być również dostępna poprzez HTTPS:

https://myapp.appspot.com/

W tym przypadku połączenia takie będzie wyorzystywać certyfikat dostarczony przez Google.

Można zarejestrować wpis DNS i stosować inną własną nazwę domeny dla swojej aplikacji, ale nie będzie się w stanie wykorzystywać protokołu HTTPS. W chwili pisania tego artykułu było to poważne ograniczenie GAE.

 top