En Juillet dernier nous vous annoncions la
refonte du blog en Jekyll hébergé sur
github pages.
Cependant, l'hébergement sur github pages ne permet pas l'utilisation d'un certificat SSL autre que celui fourni.
Ce certificat étant prévu pour une utilisation sur le domaine github.io
, nous ne pouvions pas l'utiliser avec notre
domaine eleven-labs.com
.
Dans ce post, je vais vous expliquer les modifications que nous avons faites afin d'utiliser Amazon Web Services pour
l'hébergement en https tout en conservant notre stratégie de déploiement continu.
Les services AWS que nous utilisons pour cela sont :
Pour simplifier les interactions avec AWS, nous allons utiliser aws-cli
afin de ne pas nous perdre dans son interface
web chargée.
Un guide d'installation est disponible sur la
documentation officielle.
Mise en place du certificat SSL/TLS avec AWS Certificate Manager
Pour le certificat SSL/TLS, nous utilisons le service AWS Certificate Manager.
Ce service gratuit permet la mise en place et le renouvellement automatiques de certificats SSL/TLS utilisables avec les
services AWS.
Demande de certificat
Les demandes de certificat via aws-cli
s'effectuent grâce à la commande aws acm request-certificate
à laquelle il faut
préciser le nom domaine via l'option --domain-name
.
Il est également possible de renseigner les options --subject-alternative-names
, --idempotency-token
et
--domain-validation-options
.
Dans notre cas la commande est :
aws acm request-certificate --domain-name "blog.eleven-labs.com" --region "us-east-1"
La subtilité de cette étape consiste à faire la demande de certificat dans la région us-east-1 (N. Virginia), sinon
le certificat ne pourra pas être utilisé sur la future distribution Amazon Cloudfront.
Validation de la propriété du domaine
Avant que l'autorité de certification d'Amazon puisse émettre le certificat, AWS Certificate Manager doit vérifier que
vous possédez ou contrôlez tous les domaines que vous avez indiqués dans la demande.
Amazon Certificate Manager effectue cela en envoyant un e-mail de validation de domaines aux adresses qui sont
enregistrées pour les domaines.
Pour chaque nom de domaine que vous incluez dans votre demande de certificat, un e-mail est envoyé à 3 adresses de
contact dans WHOIS et 5 adresses d'administration système courantes pour votre domaine.
Ainsi, jusqu'à 8 messages électroniques seront envoyés pour chaque nom de domaine que vous spécifiez dans votre demande.
Pour valider, vous devez donner suite à 1 de ces 8 messages dans un délai de 72 heures.
Cet e-mail est envoyé aux trois adresses de contact suivantes dans WHOIS :
- Propriétaire du domaine
- Contact technique
- Contact administratif
L'e-mail est également envoyé aux cinq adresses d'administration système courantes suivantes :
- administrator@votre_domaine
- hostmaster@votre_domaine
- postmaster@votre_domaine
- webmaster@votre_domaine
- admin@votre_domaine
Déploiement sur Amazon S3 avec Travis CI
Pour l'hébergement de notre blog Jekyll buildé, nous avons opté pour Amazon S3.
Amazon S3 pour Simple Storage Service, est un service de stockage d'objets qui peut-être configuré pour servir les
objets du bucket comme site web statique.
Cette solution est idéale dans notre cas car une fois buildé, le blog est un ensemble de fichiers statiques qui peuvent
être servis tels quels, nous évitant ainsi l'utilisation et les coûts d'une VM de type EC2 qui n'aurait servi qu'à
héberger un serveur web.
Création du bucket S3
La première étape consiste à créer le bucket S3 qui stockera nos fichiers statiques buildés.
Pour cela on utilise la
commande aws s3api create-bucket
à laquelle il faut préciser le nom du bucket via l'option --bucket
.
La subtilité de cette étape, consiste a nommer le bucket comme le FQDN du site.
Dans notre cas, le site doit être accessible à l'url blog.eleven-labs.com
, le bucket doit donc être nommé
blog.eleven-labs.com
La commande à exécuter dans notre cas est :
aws s3api create-bucket --bucket "blog.eleven-labs.com"
Configuration du bucket en site web
La seconde étape est la configuration du bucket en site web statique.
Nous utilisons ce coup-ci la commande aws s3api put-bucket-website
en lui précisant le nom du bucket via l'option
--bucket
ainsi qu'un peu de configuration pour notre site web avec l'option --website-configuration
à laquelle il
faut fournir un fichier json.
fichier: blog_eleven-labs_com.s3.json
{
"ErrorDocument": {
"Key": "404.html"
},
"IndexDocument": {
"Suffix": "index.html"
}
}
Dans la configuration du site web, nous renseignons la clé du fichier d'erreur (ErrorDocument
) ainsi que le suffixe
des documents d'index (IndexDocument
).
Il est ici question de suffixe car un bucket S3 n'est pas un système de fichier, tous les documents sont à plat et les
slashs /
font partie intégrante du nom de fichier, on parle alors de clé.
Ce système permet de simuler la fonctionnalité de DirectoryIndex
de la plupart des serveurs web traditionnels.
Nous utilisons alors la commande :
aws s3api put-bucket-website --bucket "blog.eleven-labs.com" --website-configuration file://blog_eleven-labs_com.s3.json
Autorisation d'accès publique en lecture seule aux objets du bucket
Maintenant que notre bucket est configuré en tant que site web, il faut également que l'on autorise un accès publique en
lecture aux objets qui se trouvent à l'intérieur.
Pour cela nous utilisons une policy
que nous attachons au bucket via la commande aws s3api put-bucket-policy
à
laquelle nous précisons le bucket via l'option --bucket
ainsi que la policy en json inline dans l'option --policy
.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::blog.eleven-labs.com/*"
}
]
}
Cette policy autorise (Allow
) un accès en lecture seule (s3:GetObject
) sur tous les objets du bucket
(arn:aws:s3:::blog.eleven-labs.com/*
) et à tout le monde (*
).
aws s3api put-bucket-policy --bucket "blog.eleven-labs.com" --policy "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"PublicReadGetObject\",\"Effect\":\"Allow\",\"Principal\":\"*\",\"Action\":\"s3:GetObject\",\"Resource\":\"arn:aws:s3:::blog.eleven-labs.com/*\"}]}"
Déploiement depuis Travis CI
Notre bucket est maintenant prêt à servir le blog, il ne nous reste plus qu'à y uploader les fichiers buildés à chaque
merge sur la branche master du repository grâce à Travis CI.
Nous utilisons déjà Travis CI pour l'intégration et le déploiement continus du blog, notamment pour la
[vérification de l'orthographe]({{ '/fr/comment-verifier-l-orthographe-de-vos-docs-depuis-travis-ci/' | prepend: site.baseurl | replace: '//', '/' }})
dans les PR mais également la création et l'upload à Algolia des données permettant la recherche dans le blog.
Pour le déploiement du blog dans notre bucket S3, il a suffit d'ajouter deux étapes au fichier de configuration
.travis.yml
.
L'étape before_deploy
qui permet de préparer le déploiement et l'étape deploy
qui effectue le déploiement.
# .travis.yml
#...
before_deploy:
- bundle exec jekyll build
- pip install --user awscli
- aws s3 rm s3://blog.eleven-labs.com --recursive
deploy:
- provider: s3
access_key_id: $AWS_ACCESS_KEY_ID
secret_access_key: $AWS_SECRET_ACCESS_KEY
region: $AWS_DEFAULT_REGION
bucket: blog.eleven-labs.com
local_dir: _site
skip_cleanup: true
on:
branch: master
#...
Dans l'étape de préparation du déploiement, nous buildons les fichiers statiques (bundle exec jekyll build
) puis nous
installons l'outil aws-cli afin de vider le bucket avant le déploiement (aws s3 rm s3://blog.eleven-labs.com --recursive
)
Dans l'étape de déploiement, nous utilisons le provider s3 supporté nativement par travis pour uploader les fichiers
buildés.
Cette étape ainsi que l'étape de préparation du déploiement ne sont déclenchées que pour la branche master.
Mise en place de Amazon CloudFront
L'autre modification que nous avons apportée à l'architecture du blog est l'ajout d'une distribution Amazon Cloudfront,
cela permet une amélioration des temps de réponse grâce à une augmentation des points de présence.
Mais cela nous a également permis d'utiliser le protocole http2 ainsi que le certificat SSL/TLS que nous avons généré au
début de l'article.
Création de la distribution Cloudfront
La commande permettant la création d'une distribution Amazon Cloudfront est aws cloudfront create-distribution
à
laquelle on fournit un fichier json de configuration via l'option --distribution-config
.
La subtilité de cette étape, consiste à utiliser une CustomOriginConfig
à la place d'une S3OriginConfig
.
Cela permet de profiter de la fonctionnalité S3 simulant le DirectoryIndex
étant donné que l'on utilise directement
le site servi par S3 au lieu du contenu du bucket.
fichier: blog_eleven-labs_com.cloudfront.json
{
"CallerReference": "1504193163145",
"Aliases": {
"Quantity": 1,
"Items": [
"blog.eleven-labs.com"
]
},
"DefaultRootObject": "index.html",
"Origins": {
"Quantity": 1,
"Items": [
{
"Id": "S3-Website-blog.eleven-labs.com.s3-website.eu-west-2.amazonaws.com",
"DomainName": "blog.eleven-labs.com.s3-website.eu-west-2.amazonaws.com",
"OriginPath": "",
"CustomHeaders": {
"Quantity": 0
},
"CustomOriginConfig": {
"HTTPPort": 80,
"HTTPSPort": 443,
"OriginProtocolPolicy": "http-only",
"OriginSslProtocols": {
"Quantity": 3,
"Items": [
"TLSv1",
"TLSv1.1",
"TLSv1.2"
]
},
"OriginReadTimeout": 30,
"OriginKeepaliveTimeout": 5
}
}
]
},
"DefaultCacheBehavior": {
"TargetOriginId": "S3-Website-blog.eleven-labs.com.s3-website.eu-west-2.amazonaws.com",
"ForwardedValues": {
"QueryString": false,
"Cookies": {
"Forward": "none"
},
"Headers": {
"Quantity": 0
},
"QueryStringCacheKeys": {
"Quantity": 0
}
},
"TrustedSigners": {
"Enabled": false,
"Quantity": 0
},
"ViewerProtocolPolicy": "redirect-to-https",
"MinTTL": 0,
"AllowedMethods": {
"Quantity": 2,
"Items": [
"HEAD",
"GET"
],
"CachedMethods": {
"Quantity": 2,
"Items": [
"HEAD",
"GET"
]
}
},
"SmoothStreaming": false,
"DefaultTTL": 86400,
"MaxTTL": 31536000,
"Compress": true,
"LambdaFunctionAssociations": {
"Quantity": 0
}
},
"CacheBehaviors": {
"Quantity": 0
},
"CustomErrorResponses": {
"Quantity": 1,
"Items": [
{
"ErrorCode": 404,
"ResponsePagePath": "/404.html",
"ResponseCode": "404",
"ErrorCachingMinTTL": 300
}
]
},
"Comment": "",
"Logging": {
"Enabled": false,
"IncludeCookies": false,
"Bucket": "",
"Prefix": ""
},
"PriceClass": "PriceClass_100",
"Enabled": true,
"ViewerCertificate": {
"ACMCertificateArn": "arn:aws:acm:us-east-1:760119988015:certificate/0324426f-d1d5-42b7-8c42-955b131b12ba",
"SSLSupportMethod": "sni-only",
"MinimumProtocolVersion": "TLSv1",
"Certificate": "arn:aws:acm:us-east-1:760119988015:certificate/0324426f-d1d5-42b7-8c42-955b131b12ba",
"CertificateSource": "acm"
},
"Restrictions": {
"GeoRestriction": {
"RestrictionType": "none",
"Quantity": 0
}
},
"WebACLId": "",
"HttpVersion": "http2",
"IsIPV6Enabled": true
}
Créons la distribution Cloudfront avec la commande:
aws cloudfront create-distribution --distribution-config file://blog_eleven-labs_com.cloudfront.json
Gestion des invalidations de cache
Le temps de cache par défaut de la distribution Cloudfront étant de 86400 secondes, nous avons maintenant besoin
d'invalider ce cache à chaque nouveau déploiement.
Pour cela nous allons utiliser une nouvelle étape du déploiement avec Travis, le after_deploy
.
Comme son nom l'indique, cette étape est déclenchée après le deploy
à condition que celui-ci soit en succès.
# .travis.yml
#...
after_deploy:
- aws configure set preview.cloudfront true
- aws cloudfront create-invalidation --distribution-id "$CLOUDFRONT_DISTRIBUTION_ID" --paths "/*"
#...
À cette étape, nous utilisons une fois de plus l'outil aws-cli auquel nous précisons que l'on veut utiliser les
fonctionnalités en preview de la command cloudfront (aws configure set preview.cloudfront true
), puis nous créons une
demande d'invalidation sur l'ensemble du cache (aws cloudfront create-invalidation --distribution-id "$CLOUDFRONT_DISTRIBUTION_ID" --paths "/*"
).
$CLOUDFRONT_DISTRIBUTION_ID
étant une variable définie dans la configuration Travis et contenant l'id de la
distribution Cloudfront.
Conclusion
Depuis la refonte du blog nous n'avons cessé d'améliorer l'expérience utilisateur en intervenant sur les aspects
graphiques et fonctionnels du blog, mais également comme nous venons de le voir, sur la sécurité, les performances et les
possibilités d'évolutions du blog.
Travis CI nous a permis de conserver notre workflow de déploiement continu et Amazon Web Services nous a permis
d'améliorer les performances et la sécurité grâce à l'utilisation de Cloudfront avec le protocole http2 et de AWS
Certificate Manager pour le certificat SSL/TLS managé.
Concernant les possibilités d'évolutions, il y a par exemple une PWA jusque là bloquée par l'absence de certificat SSL.
Un autre point que j'aimerais éclaircir concerne les coûts de cette architecture.
Nous sommes passés d'un hébergement Github Pages gratuit mais qui ne nous satisfaisait pas pleinement, à cette architecture
payante mais plus flexible et répondant à nos attentes.
Les coûts de cette architecture sont relativement compliqués à estimer étant donné que la facturation pour les services
Amazon Cloudfront et Amazon S3 sont principalement liés à l'utilisation.
L'utilisation de ces services est calculée en fonction du volume de données tranférés pour Cloudfront et du volume de
données stockées sur S3.
Cette solution reste tout de même économique comparée à un hébergement traditionnel composé de serveurs web sur Amazon EC2,
auxquels peuvent s'ajouter un load balancer pour les sites ayant un traffic plus soutenu.
Cette migration n'a rencontré aucun problème en particulier et s'est déroulée de manière totalement transparente pour
les utilisateurs, à l'exception de l'utilisation du protocole https, qui était le but recherché.
Cette migration a également entraîné la mise en place de "live preview" des pull requests, le fonctionnement étant très
proche de ce que l'on vient de voir ensemble, mais je garde ce sujet pour un potentiel futur article sur notre blog.