Si vous souhaitez créer une petite application Web, en Python, vous pouvez utiliser Flask ou Bottle. ) Peut être utilisé. Ces frameworks peuvent être réalisés en utilisant des décorateurs Python pour prendre en charge «quelle URL» et «quel programme s'exécute». Par exemple, l'application Flask suivante implémente un serveur Web qui renvoie «Hello, World!» Lorsque l'accès HTTP est effectué sur «/», et la réponse de la réponse de routage est très facile à comprendre.
Cité de la page officielle de Flask
# https://flask.palletsprojects.com/en/1.1.x/quickstart/
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello, World!'
Désormais, ces applications Web fonctionnent correctement lorsqu'elles sont exécutées localement à des fins de développement.
Cependant, si vous essayez de l'exécuter sur un autre serveur en tant qu'opération de production, vous devez vous procurer un serveur accessible ou exécuter un middleware tel que nginx
+ ʻuwsgi / gunicorn`, donc de l'achèvement du développement à la production L'impression est qu'il y a beaucoup de choses à préparer avant l'opération.
Surtout pour le serveur, déterminez si le serveur fonctionne normalement indépendamment de l'application, comme le coût de fonctionnement (coût réel), le fait que le middleware en cours d'exécution est en panne et que le service ne peut pas être fourni, le disque est plein, etc. Il y a une idée que si possible, je ne veux pas utiliser le serveur en premier lieu, comme la nécessité de gérer l'expiration de la prise en charge de la sécurité pour le système d'exploitation et le middleware.
De cette motivation
Je me suis demandé si ces deux points pouvaient être résolus et, par conséquent, j'ai pu y parvenir en utilisant le microframework Chalice pour AWS Lambda, donc je vais vous présenter cette méthode. ..
TL; DR
--Utilisez text / html
dans la réponse de Chalice
@ app.route
doit être configuré pour recevoir POST à partir du formulaire HTML
--Utilisez CloudFront pour diffuser des fichiers statiques vers S3 Origin, sinon vers API Gateway déployé avec Chalice, mais en utilisant le chemin d'origine de CloudFront, accès de niveau supérieur (
https: // ), vous pouvez accéder
/ ʻ de API Gateway à une étape spécifique.Cet article traite de nombreuses ressources disponibles sur AWS.
--API Gateway + AWS Lambda (automatisé par Chalice) --CloudFront (le premier point de terminaison à utiliser pour l'accès au site) --Amazon S3 (emplacement des fichiers statiques tels que image / css / js)
Les paramètres de base et l'utilisation de chaque service ne sont pas traités dans cet article.
Une illustration de ces relations est la suivante.
Je déploie tout le code sur Gtihub en raison de la quantité modérée de code.
https://github.com/t-kigi/chalice-lambda-webserver-example
Le code est cité de temps en temps, mais consultez ce référentiel pour le flux global.
L'environnement de développement est Ubuntu 18.04.
$ pipenv --version
pipenv, version 2018.11.26
$ pipenv run chalice --version
chalice 1.20.0, python 3.8.2, linux 5.4.0-48-generic
Pour la configuration de Chalice etc. J'ai écrit un article avant, veuillez vous y référer. Cette fois, le projet est créé en tant que «serveur de nouveau projet calice».
Le comportement par défaut de Chalice est de l'utiliser à des fins d'API, donc si vous ne définissez rien de particulier, vous obtiendrez une réponse de ʻapplication / json. Cependant, lors du retour d'une page qui peut être affichée par un navigateur comme une application Web, il est préférable de renvoyer une réponse de
text / html. Pour ce faire, vous devez définir le ContentType de l'en-tête de réponse sur
text / html`. Lors de l'implémentation de l'exemple Flask ci-dessus avec Chalice, le code est le suivant.
Lorsque l'exemple de Flask est réécrit dans Chalice(app.py)
from chalice import Chalice, Response
app = Chalice(app_name='server')
@app.route('/')
def index():
return Response(
status_code=200,
headers={'Content-Type': 'text/html'},
body='Hello, World!')
Au fait, si vous en définissez autant, le contenu écrit dans le corps sera renvoyé côté client avec text / html
.
En d'autres termes, en créant une page à l'aide du moteur de modèle et en envoyant le résultat final au corps, vous pouvez faire la même chose qu'une application Web qui utilise le moteur de modèle.
J'utilise jinja2
comme moteur de modèle ici, mais si vous souhaitez utiliser un moteur de modèle différent, c'est très bien.
Cette fois, la structure des répertoires est la suivante (extrait des seules parties nécessaires)
.
├── app.py #Le point d'entrée de Chalice
└── chalicelib #Tous les fichiers à déployer doivent être sous chalicelib
├── __init__.py
├── common.py #Placez des éléments communs pour en appeler d'autres depuis un autre module
├── template.py # chalicelib/Fonctions qui chargent des modèles à partir de modèles, etc.
└── templates #Mettez le modèle sous ceci
├── index.tpl
└── search.tpl
chalicelib/common.py(Extrait)
#Objet Chalice partagé par plusieurs fichiers
app = Chalice(app_name='server')
#Le chemin du répertoire où se trouve le projet
chalicelib_dir = os.path.dirname(__file__)
project_dir = os.path.dirname(chalicelib_dir)
chalicelib/modèles Paramètres pouvant obtenir les fichiers modèles suivants(template.py)
import os
from jinja2 import Environment, FileSystemLoader, select_autoescape
from chalicelib.common import project_dir
template_path = os.path.join(project_dir, 'chalicelib/templates')
loader = FileSystemLoader([template_path])
jinja2_env = Environment(
loader=loader,
autoescape=select_autoescape(['html', 'xml']))
def get(template_path):
'''Obtenir un modèle'''
return jinja2_env.get_template(template_path)
Un exemple de lecture de chalicelib / templates / index.tpl
depuis ʻapp.py` est le suivant.
app.py
from chalicelib import template
from chalicelib.common import app
def html_render(template_path, **params):
'''Renvoie la réponse de rendu HTML'''
tpl = template.get(template_path)
return Response(
status_code=200,
headers={'Content-Type': 'text/html'},
body=tpl.render(**params))
@app.route('/')
def index():
'''Retour haut de page'''
return html_render('index.tpl')
Utilisez la commande calice local
pour la vérification dans l'environnement local.
Cependant, je souhaite souvent définir des paramètres différents pour l'environnement local et l'environnement post-déploiement, il est donc recommandé de créer une étape locale
pour la vérification locale. Si vous créez une scène locale, par exemple
--Accédez aux ressources AWS en utilisant le profil uniquement dans la scène locale (le rôle IAM est utilisé pendant la production, le profil n'est donc pas utilisé) --Définissez un itinéraire qui n'est valable que dans l'étape locale
Peut être réalisé
Ajoutez la portée local
aux étapes
dans .chalice / config.json
pour créer la scène locale.
{
"version": "2.0",
"app_name": "server",
"stages": {
"dev": {
"api_gateway_stage": "v1"
},
"local": {
"environment_variables": {
"STAGE": "local"
}
}
}
}
Après avoir ajouté ceci, démarrez-le en tant que calice local --stage local
(s'il est déjà en cours d'exécution, arrêtez le processus et redémarrez-le). Désormais, la variable d'environnement STAGE
sera définie avec la valeur local
uniquement lorsqu'elle est exécutée comme --stage local
.
Les fichiers statiques à usage principal utilisés dans le développement sont les images, le CSS et le code JavaScript. Par conséquent, le chemin pour les placer est décidé, et lors de l'accès, ces fichiers sont lus et renvoyés.
J'ai finalement téléchargé ces fichiers sur S3 et les ai mis en dehors de chalicelib
parce que je ne veux pas qu'ils soient inclus dans les téléchargements de Lambda. La structure de répertoires préparée est la suivante.
Extrait de la seule partie nécessaire pour expliquer le fichier statique
.
├── server
│ ├── app.py
│ ├── chalicelib
│ │ ├── __init__.py
│ │ ├── common.py
│ │ └── staticfiles.py
│ └── static -> ../static
└── static
├── css
│ └── style.css
├── images
│ ├── sample.png
│ └── sub
│ └── sample.jpg
└── js
└── index.js
Si vous exécutez le serveur avec calice local --stage local
, le serveur sera configuré à localhost: 8000
. Par conséquent, ici, je voudrais le configurer de sorte que static / images / sample.png
puisse être obtenu en accédant à http: // localhost: 8000 / images / sample.png
.
Pour y parvenir, j'ai préparé chalicelib / staticfiles.py
.
chalicelib/staticfiles.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
'''
Une implémentation qui renvoie un fichier statique qui fonctionne avec le calice local.
CloudFront en production->Parce que cela sera traité par le chemin vers S3
Il est destiné à être utilisé uniquement pendant le développement.
'''
import os
from chalice import Response
from chalice import NotFoundError
from chalicelib.common import app, project_dir
def static_filepath(directory, file, subdirs=[]):
'''Générer et renvoyer le chemin du fichier statique sur le serveur local'''
pathes = [f for f in ([directory] + subdirs + [file]) if f is not None]
filepath = os.path.join(*pathes)
localpath = os.path.join(project_dir, 'static', filepath)
return (f'/{filepath}', localpath)
def static_content_type(filepath):
'''Contenu du fichier statique-Type de retour'''
(_, suffix) = os.path.splitext(filepath.lower())
if suffix in ['.png', '.ico']:
return 'image/png'
if suffix in ['.jpg', '.jpeg']:
return 'image/jpeg'
if suffix in ['.css']:
return 'text/css'
if suffix in ['.js']:
return 'text/javascript'
return 'application/json'
def load_static(access, filepath, binary=False):
'''Lire le fichier statique'''
try:
with open(filepath, 'rb' if binary else 'r') as fp:
data = fp.read()
return Response(
body=data, status_code=200,
headers={'Content-Type': static_content_type(filepath)})
except Exception:
raise NotFoundError(access)
@app.route('/favicon.ico', content_types=["*/*"])
def favicon():
(access, filepath) = static_filepath(None, 'favicon.ico')
return load_static(access, filepath, binary=True)
@app.route('/images/{file}', content_types=["*/*"])
@app.route('/images/{dir1}/{file}', content_types=["*/*"])
def images(dir1=None, file=None):
'''
Réponse du fichier image pour l'environnement local
(Si vous déployez sur Lambda, cela ne fonctionnera pas en raison du chemin, alors diffusez-le sur S3 avec CloudFront)
'''
(access, filepath) = static_filepath('images', file, [dir1])
return load_static(access, filepath, binary=True)
@app.route('/css/{file}', content_types=["*/*"])
@app.route('/css/{dir1}/{file}', content_types=["*/*"])
def css(dir1=None, file=None):
'''
Réponse du fichier CSS pour l'environnement local
(Si vous déployez sur Lambda, cela ne fonctionnera pas en raison du chemin, alors diffusez-le sur S3 avec CloudFront)
'''
(access, filepath) = static_filepath('css', file, [dir1])
return load_static(access, filepath)
@app.route('/js/{file}', content_types=["*/*"])
@app.route('/js/{dir1}/{file}', content_types=["*/*"])
def js(dir1=None, file=None):
'''
Réponse de fichier JS pour l'environnement local
(Si vous déployez sur Lambda, cela ne fonctionnera pas en raison du chemin, alors diffusez-le sur S3 avec CloudFront)
'''
(access, filepath) = static_filepath('js', file, [dir1])
return load_static(access, filepath)
C'est un module qui a une fonction pour lire les fichiers sous static
et ne renvoyer la réponse que lorsque le chemin est spécifique. Pour activer ceci uniquement pour la scène locale
app.py(Extrait)
import os
stage = os.environ.get('STAGE', 'dev')
if stage == 'local':
#Utilisé uniquement en local
from chalicelib import staticfiles # noqa
Vous pouvez charger staticfiles
comme ( # noqa
charge le module à associer à @ app.route
, mais ʻapp.py` donne un avertissement qu'il n'est pas utilisé directement). ..
Si vous accédez à http: // localhost: 8000
dans cet état et que l'image / CSS / JS est correctement appliquée, le chargement de la ressource statique réussit.
$ pipenv run chalice local --stage local
Serving on http://127.0.0.1:8000
127.0.0.1 - - [23/Sep/2020 17:56:13] "GET / HTTP/1.1" 200 -
127.0.0.1 - - [23/Sep/2020 17:56:14] "GET /css/style.css HTTP/1.1" 200 -
127.0.0.1 - - [23/Sep/2020 17:56:14] "GET /images/sample.png HTTP/1.1" 200 -
127.0.0.1 - - [23/Sep/2020 17:56:14] "GET /js/index.js HTTP/1.1" 200 -
L'écran affiché en accédant à http: // localhost: 8000
avec Chrome est le suivant.
Avec ce montant, une simple vérification peut être effectuée localement.
La méthode d'ajout des chemins ci-dessus un par un n'est pas réaliste à ajouter chaque fois que l'extension ou le chemin cible augmente.
Dans un tel cas, vous pouvez définir nginx localement et utiliser le paramètre de serveur qui décrit l'emplacement de sorte que la racine soit statique
uniquement pour un chemin spécifique. Après cela, vous pouvez continuer à accéder et à vérifier sur http: // localhost /.
Cet article n'explique pas comment utiliser nginx, mais si cela se complique, vous pouvez envisager de l'introduire, je vais donc le présenter comme référence.
nginx.Exemple spécifique de conf
location / {
#Cliquez ici pour un accès normal
proxy_pass http://localhost:8000;
}
location ~ ^/(images|css|js)/ {
#Obtenir des ressources statiques à partir d'un chemin fixe
root (Chemin du projet)/static;
}
Lors du POST depuis le formulaire du navigateur, le Content-Type est envoyé sous la forme ʻapplication / x-www-form-urlencodedou
multipart / form-data`.
Le côté Calice n'accepte pas cela par défaut, il est donc nécessaire de permettre de les recevoir dans les content_types de la méthode correspondante.
chalicelib/common.py
post_content_types = [
'application/x-www-form-urlencoded',
'multipart/form-data'
]
def post_params():
'''dict renvoie les paramètres envoyés à la méthode post'''
def to_s(s):
try:
return s.decode()
except Exception:
return s
#Convertir en type str et retourner
body = app.current_request.raw_body
parsed = dict(parse.parse_qsl(body))
return {to_s(k): to_s(v) for (k, v) in parsed.items()}
app.py
@app.route('/search', methods=['POST'],
content_types=common.post_content_types)
def search():
'''Rechercher'''
params = common.post_params() #Obtenir des paramètres sous la forme de dict
Si vous disposez d'un IAM largement privilégié avec accès par programme, vous pouvez déployer votre application avec une seule commande calice deploy
.
D'un autre côté, si ce n'est pas le cas, ou si vous souhaitez utiliser des outils CI / CD, vous pouvez utiliser la commande calice package
pour créer une boîte à outils qui peut être déployée avec CloudFormation.
Des exemples spécifiques sont les suivants. Puisque «--profile» et «--region» sont omis, ajoutez-les si nécessaire.
chalice_Exemple d'utilisation de package
BUCKET=<Spécifiez le compartiment S3 pour télécharger des ressources pour CloudFormation>
#Convertir en méthode de déploiement CloudFormation
$ pipenv run chalice package build
$ cd build
#Package et télécharger sur S3
$ aws cloudformation package --template-file sam.json \
--s3-bucket ${BUCKET} --output-template-file chalice-webapp.yml
#Déployer avec CloudFormation
$ aws cloudformation deploy --template-file chalice-webapp.yml --stack-name <Nom de la pile> --capabilities CAPABILITY_IAM
Cette fois, nous utiliserons le résultat du déploiement avec la commande calice deploy
. Puisque «--stage» n'est pas spécifié, le paramètre d'étape «dev» est utilisé ici (de même, «--region» et «--profile» sont omis).
$ pipenv run chalice deploy
Creating deployment package.
Reusing existing deployment package.
Creating IAM role: server-dev
Creating lambda function: server-dev
Creating Rest API
Resources deployed:
- Lambda ARN: arn:aws:lambda:ap-northeast-1:***********:function:server-dev
- Rest API URL: https://**********.execute-api.ap-northeast-1.amazonaws.com/v1/
En accédant à l'URL déployée, text / html
sera renvoyé correctement.
$ curl https://**********.execute-api.ap-northeast-1.amazonaws.com/v1/
<!DOCTYPE html>
<html lang="ja">
<head>
<title>HELLO</title>
<meta charset="UTF-8">
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
<h1>Lambda Web Hosting</h1>
<p>AWS Lambda en tant que backend+ Chalice (Python)Avec flacon/C'est un échantillon qui bouge comme une bouteille.</p>
<h2>Load Static Files</h2>
<p>Un autre fichier dans la balise h1/css/style.Le style lu à partir de css est correct.</p>
<p>L'image est la suivante.</p>
<img src="/images/sample.png "/><br>
<p>JavaScript est également chargé.</p>
<span id="counter">0</span><br>
<button id="button" type="button">compteur(Appuyez sur le bouton pour ajouter les chiffres)</button>
<h2>Form Post</h2>
<p>Obtenez la correspondance de la base de données fictive que vous avez en interne.</p>
<form method="POST" action="/search">
<label>Mot-clé de recherche: </label>
<input type="text" name="keyword" value="" />
<br>
<button type="submit">Chercher</button>
</form>
<script src="/js/index.js"></script>
</body>
</html>
Créez un compartiment S3 quelque part afin que le contenu ici puisse être référencé à partir de CloudFront.
Cette fois, j'ai préparé un bucket sample-bucket.t-kigi.net
.
Cela peut être téléchargé / mis à jour par lots comme suit en utilisant, par exemple, ʻawscli`.
#Déplacer à la racine du fichier statique
cd static
#Copier tous les fichiers
$ aws s3 sync . s3://sample-bucket.t-kigi.net/
upload: css/style.css to s3://sample-bucket.t-kigi.net/css/style.css
upload: js/index.js to s3://sample-bucket.t-kigi.net/js/index.js
upload: ./favicon.ico to s3://sample-bucket.t-kigi.net/favicon.ico
upload: images/sub/sample.jpg to s3://sample-bucket.t-kigi.net/images/sub/sample.jpg
upload: images/sample.png to s3://sample-bucket.t-kigi.net/images/sample.png
Définissez les paramètres suivants pour configurer une distribution CloudFront en tant que source d'accès au site Web. De plus, toutes les procédures sont effectuées dans la console gérée.
--Créez un certificat SSL pour le domaine cible dans ** us-east-1 avec Certification Manager
--CloudFront ne spécifie pas de région, donc toutes les ressources utilisées doivent être créées dans us-east-1.
--Cette fois, le certificat est activé et automatiquement renouvelé en ajoutant un enregistrement prouvant que vous êtes administrateur du domaine de la zone hébergée gérée par Route53.
--Sélectionnez Créer une distribution
> Web
pour créer une distribution et définir les paramètres suivants
--Saisissez la passerelle API ** FQDN ** pour le nom de domaine d'origine (par exemple **********. Execute-api.ap-northeast-1.amazonaws.com
)
--Supplément: Si vous collez l'URI, ce chemin d'origine et le suivant seront définis sur les valeurs appropriées.
--Placez l'étape API Gateway dans le chemin d'origine pour l'accès à la page supérieure (par exemple / v1
)
--Définissez la stratégie de protocole de la visionneuse sur Rediriger HTTP vers HTTPS
(car API Gateway n'accepte que HTTPS)
lambdasite.t-kigi.net
) dans les autres noms de domaine des paramètres de distribution.
--Spécifiez un certificat SSL pré-créé dans le certificat SSL
―― Immédiatement après la création du certificat, il peut ne pas apparaître dans les options, alors dans ce cas, attendez un moment
--S'il n'apparaît toujours pas, vérifiez à nouveau si la région créée est us-east-1.
--Ne saisissez rien dans l'objet racine par défaut **/ images / *
, / css / *
, / js / *
, / favicon.ico
à S3 Origin (voir l'image ci-dessous)Suivez les étapes ci-dessus et attendez que l'état CloudFront soit déployé. Cela prend environ 10 à 20 minutes.
Les exemples de sites qui ont été déployés ci-dessus sont les suivants.
https://lambdasite.t-kigi.net
Cette procédure transmet le «/» de CloudFront au «/ v1 /» d'API Gateway, vous permettant de gérer l'accès au niveau supérieur du site.
Si vous voulez transférer vers / v1
-> / v1
tel quel, vous n'avez rien à entrer dans ʻOrigin Path` du paramètre d'origine.
Le nombre par défaut d'exécutions Lambda simultanées est de 1000, donc si vous le combinez avec le cache CDN, vous pouvez vous attendre à gérer un grand nombre d'accès simultanés.
REMARQUE: chaque prix est basé sur celui de la région ap-nord-est-1 (Tokyo) au moment de la rédaction de l'article du 23 septembre 2020.
Si vous quittez t2.micro, qui est gratuit pendant (1 an) sur AWS, les frais de 30 jours (1 mois)
--Frais d'opération d'instance: 10,944 USD = (0,0152 USD / heure * 24 heures * 30 jours)
(0,12 USD / mois * 8)
--Autre volume de transfert de données en dehors d'AWS (0,114 USD / Go)S'il s'agit d'un petit système, il sera d'environ 1200 à 1300 yens (au moment de la rédaction de l'article). Il sera gratuit dans les 12 mois suivant la création d'un compte AWS, mais s'il le dépasse, ou si plusieurs comptes sont créés et liés au sein d'une même entreprise, le niveau gratuit disparaîtra (histoire d'expérience) .. Cependant, il s'agit d'un système qui ne tient pas compte de la redondance et on suppose que si l'instance tombe en panne, elle sera traitée manuellement. Si vous voulez le rendre redondant, vous devez préparer deux (ou plus) serveurs ou mettre ALB en première ligne (forfait mensuel + environ 20 USD), ce qui pose un simple problème de coût.
D'autre part, si vous utilisez AWS Lambda
(400 000 Go-secondes ÷ 0,125 Go = 3,2 millions de secondes)
minutes est gratuit par mois
――Même s'il y a plus d'un million de demandes, 0,2 USD pour 1 million ensuiteDans une telle forme, si le serveur est rarement consulté, il sera possible de le faire fonctionner suffisamment avec environ 1 à 2 USD. Une simple comparaison présente un gros avantage, mais il convient également de noter que «chaque service AWS utilisé ici est redondant». Il peut être difficile pour Lambda de gérer toutes les données telles que des dizaines de milliers de requêtes par seconde, mais si vous utilisez l'exécution parallèle du cache de CloudFront et d'AWS Lambda, vous pouvez gérer des services avec des centaines de requêtes par seconde sans effort particulier. Il y a une possibilité. L'inconvénient du mécanisme de Lambda est que le premier démarrage peut prendre un certain temps lorsqu'il n'y a pas d'accès et que le temps de réponse peut ne pas être stable.
Si votre site Web se compose uniquement de contenu statique, vous pouvez le faire avec CloudFront --S3 seul. En outre, divers contenus statiques sont placés dans S3, l'API est fournie par API Gateway et certains chemins sont directement connectés à API Gateway par CloudFront pour créer une SPA (application à page unique) qui ne se soucie pas de CORS. Je pense que c'est une façon plus moderne de l'utiliser.
Cependant, cette fois-ci, il était nécessaire d'exécuter le moteur de modèle en utilisant les données échangées sur le back-end, il y a donc un historique de réflexion sur une conception qui utilise AWS Lambda comme origine par défaut et permet à la communication d'y circuler.
J'ai présenté comment implémenter une application Web telle que Flask / Bottle, qui est sans serveur en utilisant diverses ressources de Chalice + AWS Lambda + AWS, et la mettre dans l'environnement de production réel. Cependant, en conséquence, un service est construit à l'aide de divers services AWS, donc je pense que c'est devenu difficile à comprendre même si vous lisez cet article sans aucune connaissance particulière.
Cependant, dans le sens de créer une application Web qui ne gère pas le serveur, on peut dire qu '«au final, nous créons un environnement où nous pouvons nous concentrer sur le codage». Cela peut sembler un détour pour comprendre le serveur et chaque infrastructure car vous ne gérez pas le serveur, mais je pense personnellement qu'il n'y a pas de perte d'apprentissage à cette époque.
Basé sur le projet présenté ici
--Utilisez DynamoDB comme magasin de données côté application
En introduisant de tels points, il est possible de créer un environnement capable de résister à des opérations plus réelles.
Recommended Posts