Configurazione di Python con App Web del servizio app di Azure

Questa esercitazione descrive le opzioni per la creazione e la configurazione di un'applicazione Python di base conforme all'interfaccia WSGI (Web Server Gateway Interface) in App Web del servizio app di Azure.

Descrive le funzionalità aggiuntive della distribuzione Git, ad esempio l'ambiente virtuale e l'installazione del pacchetto con requirements.txt.

Bottle, Django o Flask?

Azure Marketplace contiene modelli per i framework Bottle, Django e Flask. Se si sta sviluppando la prima app Web nel servizio app di Azure o non si ha familiarità con Git, è consigliabile seguire una delle esercitazioni seguenti, che includono istruzioni dettagliate per la creazione di un'applicazione funzionante dalla raccolta tramite la distribuzione Git da Windows o Mac:

Creazione di un'app Web nel portale di Azure

Per seguire questa esercitazione è necessaria una sottoscrizione di Azure e l'accesso al portale di Azure.

Se non si dispone di un'app Web esistente, è possibile crearne una dal portale di Azure. Fare clic sul pulsante NUOVO nell'angolo superiore sinistro e quindi su Web e dispositivi mobili > App Web.

Pubblicazione Git

Configurare la pubblicazione Git per l'app Web appena creata seguendo le istruzioni disponibili in Distribuzione del repository Git locale nel servizio app di Azure. Questa esercitazione usa Git per creare, gestire e pubblicare l'app Web Python nel servizio app di Azure.

Dopo aver configurato la pubblicazione Git, verrà creato un repository che verrà associato all'app Web. L'URL del repository verrà visualizzato e potrà pertanto essere usato per effettuare il push dei dati dall'ambiente di sviluppo locale al cloud. Per pubblicare applicazioni tramite Git, assicurarsi che sia stato installato anche il client Git e attenersi alle istruzioni fornite per eseguire il push dei contenuti dell'app Web nel servizio app di Azure.

Informazioni generali sull'applicazione

Nelle sezioni successive vengono creati i seguenti file. Devono essere posizionati nella radice del repository Git.

app.py
requirements.txt
runtime.txt
web.config
ptvs_virtualenv_proxy.py

Gestore WSGI

WSGI è uno standard Python descritto da PEP 3333 che definisce un'interfaccia tra il server Web e Python che consente di scrivere varie applicazioni e framework Web tramite Python. Oggi i più comuni framework Web Python usano l'interfaccia WSGI. App Web del servizio app di Azure offre assistenza per qualsiasi framework. Inoltre, gli utenti avanzati possono anche creare il proprio framework a patto che il gestore personalizzato si attenga alle linee guida delle specifiche WSGI.

Di seguito è riportato un esempio di un app.py che definisce un gestore personalizzato:

def wsgi_app(environ, start_response):
    status = '200 OK'
    response_headers = [('Content-type', 'text/plain')]
    start_response(status, response_headers)
    response_body = 'Hello World'
    yield response_body.encode()

if __name__ == '__main__':
    from wsgiref.simple_server import make_server

    httpd = make_server('localhost', 5555, wsgi_app)
    httpd.serve_forever()

È possibile eseguire l'applicazione localmente con python app.py, quindi passare a http://localhost:5555 nel Web browser.

Ambiente virtuale

Sebbene l'app dell'esempio precedente non necessiti di pacchetti esterni, è probabile che l'applicazione creata ne richieda alcuni.

Per gestire le dipendenze di pacchetti esterni, la distribuzione Git di Azure supporta la creazione di ambienti virtuali.

Quando Azure rileva un file requirements.txt nella radice del repository, crea automaticamente un ambiente virtuale denominato env. Questo si verifica solo durante la prima distribuzione o durante una qualsiasi distribuzione dopo che è cambiato il runtime di Python selezionato.

È opportuno creare un ambiente virtuale locale per lo sviluppo, ma evitare di includerlo nel repository Git.

Gestione dei pacchetti

I pacchetti elencati in requirements.txt verranno installati automaticamente nell'ambiente virtuale tramite pip. Ciò avviene in ogni distribuzione, tuttavia pip non eseguirà l'installazione se un pacchetto è già installato.

Esempio requirements.txt:

azure==0.8.4

Versione di Python

Per determinare la versione di Python da usare per l'ambiente virtuale di Python, viene applicata la seguente priorità:

  1. versione specificata nel file runtime.txt nella cartella radice
  2. versione specificata dall'impostazione di Python nella configurazione dell'app Web (pannello Impostazioni > Impostazioni applicazione dell'app Web nel portale di Azure)
  3. Se non viene specificata nessuna delle versioni sopra indicate, verrà usata quella predefinita, ovvero python-2.7.

I valori validi per il contenuto di

\runtime.txt

sono:

  • python-2.7
  • python-3.4

L'eventuale microversione (terza cifra) specificata verrà ignorata.

Esempio runtime.txt:

python-2.7

Web.config

È necessario creare un file web.config per specificare il modo in cui il server deve gestire le richieste.

Si noti che se nel repository si dispone di un file web.x.y.config, dove x.y corrisponde al runtime di Python selezionato, Azure copierà automaticamente il file appropriato come web.config.

I seguenti esempi di file web.config si basano su uno script proxy dell'ambiente virtuale, descritto nella sezione successiva. Funzionano con il gestore WSGI usato nell' app.py di esempio precedente.

web.config di esempio per Python 2.7:

<?xml version="1.0"?>
<configuration>
  <appSettings>
    <add key="WSGI_ALT_VIRTUALENV_HANDLER" value="app.wsgi_app" />
    <add key="WSGI_ALT_VIRTUALENV_ACTIVATE_THIS"
         value="D:\home\site\wwwroot\env\Scripts\activate_this.py" />
    <add key="WSGI_HANDLER"
         value="ptvs_virtualenv_proxy.get_virtualenv_handler()" />
    <add key="PYTHONPATH" value="D:\home\site\wwwroot" />
  </appSettings>
  <system.web>
    <compilation debug="true" targetFramework="4.0" />
  </system.web>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true" />
    <handlers>
      <remove name="Python27_via_FastCGI" />
      <remove name="Python34_via_FastCGI" />
      <add name="Python FastCGI"
           path="handler.fcgi"
           verb="*"
           modules="FastCgiModule"
           scriptProcessor="D:\Python27\python.exe|D:\Python27\Scripts\wfastcgi.py"
           resourceType="Unspecified"
           requireAccess="Script" />
    </handlers>
    <rewrite>
      <rules>
        <rule name="Static Files" stopProcessing="true">
          <conditions>
            <add input="true" pattern="false" />
          </conditions>
        </rule>
        <rule name="Configure Python" stopProcessing="true">
          <match url="(.*)" ignoreCase="false" />
          <conditions>
            <add input="{REQUEST_URI}" pattern="^/static/.*" ignoreCase="true" negate="true" />
          </conditions>
          <action type="Rewrite"
                  url="handler.fcgi/{R:1}"
                  appendQueryString="true" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>

web.config di esempio per Python 3.4:

<?xml version="1.0"?>
<configuration>
  <appSettings>
    <add key="WSGI_ALT_VIRTUALENV_HANDLER" value="app.wsgi_app" />
    <add key="WSGI_ALT_VIRTUALENV_ACTIVATE_THIS"
         value="D:\home\site\wwwroot\env\Scripts\python.exe" />
    <add key="WSGI_HANDLER"
         value="ptvs_virtualenv_proxy.get_venv_handler()" />
    <add key="PYTHONPATH" value="D:\home\site\wwwroot" />
  </appSettings>
  <system.web>
    <compilation debug="true" targetFramework="4.0" />
  </system.web>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true" />
    <handlers>
      <remove name="Python27_via_FastCGI" />
      <remove name="Python34_via_FastCGI" />
      <add name="Python FastCGI"
           path="handler.fcgi"
           verb="*"
           modules="FastCgiModule"
           scriptProcessor="D:\Python34\python.exe|D:\Python34\Scripts\wfastcgi.py"
           resourceType="Unspecified"
           requireAccess="Script" />
    </handlers>
    <rewrite>
      <rules>
        <rule name="Static Files" stopProcessing="true">
          <conditions>
            <add input="true" pattern="false" />
          </conditions>
        </rule>
        <rule name="Configure Python" stopProcessing="true">
          <match url="(.*)" ignoreCase="false" />
          <conditions>
            <add input="{REQUEST_URI}" pattern="^/static/.*" ignoreCase="true" negate="true" />
          </conditions>
          <action type="Rewrite" url="handler.fcgi/{R:1}" appendQueryString="true" />
        </rule>
      </rules>
    </rewrite>
  </system.webServer>
</configuration>

I file statici verranno gestiti direttamente dal server Web, senza passare per il codice Python, per migliorare le prestazioni.

Negli esempi precedenti il percorso dei file statici sul disco deve corrispondere al percorso nell'URL. Ciò significa che una richiesta per http://pythonapp.azurewebsites.net/static/site.css servirà il file sul disco nel percorso \static\site.css.

WSGI_ALT_VIRTUALENV_HANDLER viene specificato il gestore WSGI. Negli esempi precedenti corrisponde a app.wsgi_app perché il gestore è una funzione denominata wsgi_app in app.py nella cartella radice.

PYTHONPATH può essere personalizzato ma, se si installano tutte le dipendenze nell'ambiente virtuale specificandole in requirements.txt, non è necessario modificarlo.

Proxy dell'ambiente virtuale

Il seguente script viene usato per recuperare il gestore WSGI, attivare l'ambiente virtuale e registrare gli errori. È progettato per essere generico e per essere usato senza doverlo modificare.

Contenuto di ptvs_virtualenv_proxy.py:

 # ############################################################################
 #
 # Copyright (c) Microsoft Corporation. 
 #
 # This source code is subject to terms and conditions of the Apache License, Version 2.0. A 
 # copy of the license can be found in the License.html file at the root of this distribution. If 
 # you cannot locate the Apache License, Version 2.0, please send an email to 
 # vspython@microsoft.com. By using this source code in any fashion, you are agreeing to be bound 
 # by the terms of the Apache License, Version 2.0.
 #
 # You must not remove this notice, or any other, from this software.
 #
 # ###########################################################################

import datetime
import os
import sys
import traceback

if sys.version_info[0] == 3:
    def to_str(value):
        return value.decode(sys.getfilesystemencoding())

    def execfile(path, global_dict):
        """Execute a file"""
        with open(path, 'r') as f:
            code = f.read()
        code = code.replace('\r\n', '\n') + '\n'
        exec(code, global_dict)
else:
    def to_str(value):
        return value.encode(sys.getfilesystemencoding())

def log(txt):
    """Logs fatal errors to a log file if WSGI_LOG env var is defined"""
    log_file = os.environ.get('WSGI_LOG')
    if log_file:
        f = open(log_file, 'a+')
        try:
            f.write('%s: %s' % (datetime.datetime.now(), txt))
        finally:
            f.close()

ptvsd_secret = os.getenv('WSGI_PTVSD_SECRET')
if ptvsd_secret:
    log('Enabling ptvsd ...\n')
    try:
        import ptvsd
        try:
            ptvsd.enable_attach(ptvsd_secret)
            log('ptvsd enabled.\n')
        except: 
            log('ptvsd.enable_attach failed\n')
    except ImportError:
        log('error importing ptvsd.\n')

def get_wsgi_handler(handler_name):
    if not handler_name:
        raise Exception('WSGI_ALT_VIRTUALENV_HANDLER env var must be set')

    if not isinstance(handler_name, str):
        handler_name = to_str(handler_name)

    module_name, _, callable_name = handler_name.rpartition('.')
    should_call = callable_name.endswith('()')
    callable_name = callable_name[:-2] if should_call else callable_name
    name_list = [(callable_name, should_call)]
    handler = None
    last_tb = ''

    while module_name:
        try:
            handler = __import__(module_name, fromlist=[name_list[0][0]])
            last_tb = ''
            for name, should_call in name_list:
                handler = getattr(handler, name)
                if should_call:
                    handler = handler()
            break
        except ImportError:
            module_name, _, callable_name = module_name.rpartition('.')
            should_call = callable_name.endswith('()')
            callable_name = callable_name[:-2] if should_call else callable_name
            name_list.insert(0, (callable_name, should_call))
            handler = None
            last_tb = ': ' + traceback.format_exc()

    if handler is None:
        raise ValueError('"%s" could not be imported%s' % (handler_name, last_tb))

    return handler

activate_this = os.getenv('WSGI_ALT_VIRTUALENV_ACTIVATE_THIS')
if not activate_this:
    raise Exception('WSGI_ALT_VIRTUALENV_ACTIVATE_THIS is not set')

def get_virtualenv_handler():
    log('Activating virtualenv with %s\n' % activate_this)
    execfile(activate_this, dict(__file__=activate_this))

    log('Getting handler %s\n' % os.getenv('WSGI_ALT_VIRTUALENV_HANDLER'))
    handler = get_wsgi_handler(os.getenv('WSGI_ALT_VIRTUALENV_HANDLER'))
    log('Got handler: %r\n' % handler)
    return handler

def get_venv_handler():
    log('Activating venv with executable at %s\n' % activate_this)
    import site
    sys.executable = activate_this
    old_sys_path, sys.path = sys.path, []

    site.main()

    sys.path.insert(0, '')
    for item in old_sys_path:
        if item not in sys.path:
            sys.path.append(item)

    log('Getting handler %s\n' % os.getenv('WSGI_ALT_VIRTUALENV_HANDLER'))
    handler = get_wsgi_handler(os.getenv('WSGI_ALT_VIRTUALENV_HANDLER'))
    log('Got handler: %r\n' % handler)
    return handler

Personalizzare la distribuzione Git

Per stabilire se l'applicazione usa Python, Azure verifica se entrambe le condizioni seguenti sono soddisfatte:

  • Presenza del file requirements.txt nella cartella radice
  • Presenza di un qualsiasi file .py nella cartella radice OPPURE di un file runtime.txt in cui viene specificato python

In tale circostanza, userà uno script di distribuzione specifico di Python che eseguirà la sincronizzazione standard dei file, oltre ad operazioni aggiuntive di Python, tra cui:

  • Gestione automatica dell'ambiente virtuale
  • Installazione dei pacchetti elencati in requirements.txt tramite pip
  • Creazione del file web.config appropriato sulla base della versione di Python selezionata
  • Raccolta di file statici per le applicazioni Django

È possibile controllare determinati aspetti della procedura di distribuzione predefinita senza dover personalizzare lo script.

Per ignorare tutti i passaggi della distribuzione specifici di Python, è possibile creare questo file vuoto:

\.skipPythonDeployment

Per un maggiore controllo sulla distribuzione, è possibile eseguire l'override dello script di distribuzione predefinito creando i file seguenti:

\.deployment
\deploy.cmd

Per creare i file, è possibile usare l'interfaccia della riga di comando di Azure. Usare questo comando dalla cartella del progetto:

azure site deploymentscript --python

Se questi file non esistono, Azure creerà uno script di distribuzione temporaneo e lo eseguirà. Tale file è identico a quello creato con il comando precedente.

Risoluzione dei problemi - Installazione dei pacchetti

Alcuni pacchetti potrebbero non essere installati tramite pip se eseguiti su Azure. Il motivo può essere semplicemente dovuto al fatto che il pacchetto non è disponibile nell'indice del pacchetto Python. Potrebbe essere necessario un compilatore (se non è disponibile alcun compilatore nel computer che esegue il sito Web di Azure).

In questa sezione vengono esaminati alcuni metodi utili per risolvere questo problema.

Richiedere i file wheel

Se per l'installazione del pacchetto è necessario un compilatore, provare a contattare il proprietario del pacchetto per richiedere che vengano resi disponibili i file wheel per il pacchetto.

Grazie alla recente disponibilità del compilatore Microsoft Visual C++ per Python 2.7, è ora più facile compilare pacchetti con codice nativo per Python 2.7.

Creare i file wheel (richiede Windows)

quando si usa questa opzione, assicurarsi di compilare il pacchetto usando un ambiente Python corrispondente alla combinazione di piattaforma/architettura/versione usata nel sito Web di Azure (Windows/32 bit/2.7 o 3.4).

Se il pacchetto non viene installato perché richiede un compilatore, è possibile installare il compilatore nel computer locale e creare un file wheel per il pacchetto, da includere quindi nel repository.

Utenti Mac/Linux: se non si ha accesso a un computer Windows, vedere Creare una macchina virtuale che esegue Windows per informazioni su come creare una VM in Azure. È possibile usare la macchina virtuale per creare i file wheel, aggiungerli al repository e quindi eliminare la macchina virtuale se lo si desidera.

Per Python 2.7, è possibile installare il compilatore Microsoft Visual C++ per Python 2.7.

Per Python 3.4, è possibile installare Microsoft Visual C++ 2010 Express.

Per creare i file wheel, sarà necessario il pacchetto wheel:

env\scripts\pip install wheel

Usare pip wheel per compilare una dipendenza:

env\scripts\pip wheel azure==0.8.4

Verrà creato un file con estensione whl nella cartella \wheelhouse. Aggiungere la cartella \wheelhouse e i file wheel nel repository.

Modificare requirements.txt per aggiungere l'opzione --find-links nella parte superiore. In questo modo, si indica a pip di cercare una corrispondenza esatta nella cartella locale prima di passare all'indice del pacchetto Python.

--find-links wheelhouse
azure==0.8.4

Per includere tutte le dipendenze nella cartella \wheelhouse senza usare del tutto l'indice del pacchetto Python, è possibile forzare pip in modo che ignori l'indice del pacchetto aggiungendo --no-index nella parte superiore di requirements.txt.

--no-index

Personalizzare l'installazione

È possibile personalizzare lo script di distribuzione in modo da installare un pacchetto nell'ambiente virtuale usando un programma di installazione alternativo, come easy_install. Per un esempio impostato come commento, vedere deploy.cmd. Assicurarsi che questi pacchetti non siano elencati in requirements.txt, in modo da impedirne l'installazione da parte di pip.

Aggiungere questa riga allo script di distribuzione:

env\scripts\easy_install somepackage

Si potrebbe anche usare easy_install per eseguire l'installazione da un programma di installazione EXE (alcuni sono compatibili con il formato ZIP e sono quindi supportati da easy_install). Aggiungere il programma di installazione nel repository e richiamare easy_install passando il percorso del file eseguibile.

Aggiungere questa riga allo script di distribuzione:

env\scripts\easy_install "%DEPLOYMENT_SOURCE%\installers\somepackage.exe"

Includere l'ambiente virtuale nel repository (richiede Windows)

quando si usa questa opzione, assicurarsi di usare un ambiente virtuale corrispondente alla combinazione di piattaforma/architettura/versione usata nel sito Web di Azure (Windows/32 bit/2.7 o 3.4).

Se si include l'ambiente virtuale nel repository, è possibile impedire allo script di distribuzione di eseguire la gestione dell'ambiente virtuale in Azure creando un file vuoto:

.skipPythonDeployment

È preferibile eliminare l'ambiente virtuale nel sito, per evitare la presenza di file rimasti in seguito alla gestione automatica dell'ambiente virtuale.

Risoluzione dei problemi - Ambiente virtuale

Lo script di distribuzione ignorerà la creazione dell'ambiente virtuale in Azure se rileva che esiste già un ambiente virtuale compatibile. Questo comportamento può accelerare notevolmente la distribuzione. I pacchetti già installati verranno ignorati da pip.

In determinate situazioni, può essere utile forzare l'eliminazione dell'ambiente virtuale, ad esempio quando si decide di includere un ambiente virtuale come parte del repository oppure quando è necessario eliminare alcuni pacchetti o testare le modifiche apportate a requirements.txt.

Sono disponibili alcune opzioni per gestire l'ambiente virtuale in Azure.

Opzione 1: Utilizzare il FTP

Con un client FTP connettersi al server per eliminare la cartella env. Tenere presente che alcuni client FTP, come i Web browser, possono essere di sola lettura e non consentire l'eliminazione di cartelle. Per questo motivo, è bene assicurarsi di usare un client FTP con questa funzionalità. Nome host e utente FTP sono visualizzati nel pannello dell'app Web nel portale di Azure.

Opzione 2: Attiva/Disattiva runtime

Ecco un'alternativa che sfrutta il fatto che lo script di distribuzione eliminerà la cartella env quando non corrisponde alla versione desiderata di Python. In questo modo viene effettivamente eliminato l'ambiente esistente e ne viene creato uno nuovo.

  1. Passare a una versione diversa di Python (tramite runtime.txt o le impostazioni dell'applicazione blade nel portale di Azure)
  2. Eseguire il push GIT di alcune modifiche (ignorare eventuali errori di installazione di pip)
  3. Tornare alla versione iniziale di Python
  4. Eseguire di nuovo il push GIT di alcune modifiche

Opzione 3: personalizzare lo script di distribuzione

Se lo script di distribuzione è stato personalizzato, è possibile modificare il codice in deploy.cmd per forzare l'eliminazione della cartella env.

Passaggi successivi

Per ulteriori informazioni, vedere il Centro per sviluppatori di Python.

Nota

Per iniziare a usare il servizio app di Azure prima di registrarsi per ottenere un account Azure, andare a Prova il servizio app, dove è possibile creare un'app Web iniziale temporanea nel servizio app. Non è necessario fornire una carta di credito né impegnarsi in alcun modo.

Modifiche apportate