Problématique
Les CMS sont aujourd'hui le choix principal lorsqu'il s'agit de créer son blog, mais l'apparition de nombreux projets de générateurs de sites/blogs statiques est en train de changer la donne. Ces générateurs de sites permettent de séparer la structure, le design, et l'écriture du contenu en Markdown ou en reStructuredText. Le but de cet article est d'automatiser le déploiement et la gestion du'un blog réalisé à partir d'un générateur de blog statique. Pour cet article on utilisera Hugo qui est un des générateurs de sites les plus rapides du marché. Vous pouvez évidemment utiliser le moteur de rendu HTML qui vous convient, il faudra juste adapter les paramètres et le buildspec.
Nous avions déjà présenté il y a quelques temps le système faisant tourner notre blog en serverless lorsque nous utilisions la CI Travis. Depuis la sortie de AWS CodeBuild, nous avons migré, d'où ce second article.
On ne s'attardera pas sur l'utilisation de Hugo en lui même mais plutôt sur la façon de mettre en ligne le blog et y ajouter du contenu depuis votre dépot Github grâce à Amazon CloudFormation et CodePipeline notamment. Pour en savoir plus sur Hugo vous pouvez visiter le site d'Hugo ou le repo GitHub du projet.
Architecture
Notre architecture a pour objectif de permettre un déploiement continu (à chaque modification du dépôt) des fichiers statiques du blog sur un bucket S3. Le contenu ajouté ou modifié depuis le dépôt GitHub déclenche la mise à jour d'un CodePipeline (1) qui va récupérer les données du dépôt, les transmettre (2) à CodeBuild pour générer les pages statiques qui seront ensuite déposées (3) dans un bucket S3 avec le web hosting activé.
Pour servir nos pages web, on crée une distribution CloudFront associée à un certificat TLS qui pointe sur le endpoint du bucket s3. L'accès au site se fait par un enregistrement dns qui est en fait un alias de la distribution CloudFront.
Pour automatiser tout ça, nous avons un template CloudFormation avec les paramètres et les ressources nécessaires sur ce dépôt GitHub.
Quelques prérequis
- Créer votre nom de domaine
Plus précisément, créer votre zone hébergée publique. La documentation AWS explique comment s'y prendre.
- Créer un certificat public
Voir comment effectuer une demande de certificat public. Et surtout penser à récupérer l'ARN du certificat pour les paramètres.
- AWS S3
On déclare notre bucket S3 qui va servir nos pages web et les policies permettant l'accès aux fichiers du bucket depuis Internet.
S3WebsiteBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref BucketName
WebsiteConfiguration:
IndexDocument: index.html
ErrorDocument: error.html
DeletionPolicy: Retain
S3WebsiteBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref S3WebsiteBucket
PolicyDocument:
Statement:
- Action:
- s3:GetObject
Effect: Allow
Resource:
Fn::Join:
- ""
-
- "arn:aws:s3:::"
-
Ref: "S3WebsiteBucket"
- "/*"
Principal: "*"
- AWS CodeBuild:
CodeBuild a besoin d'un Service Role que l'on déclare avant de créer la ressource CodeBuild en elle même. Les propriétés de l'environnement de build sont décrites ensuite. Lors de l'exécution de CodeBuild, les spécifications de génération du code sont déclarées dans un fichier buildspec.yml qui se trouve à la racine de la source (le repo GitHub).
CodeBuildServiceRole:
Type: AWS::IAM::Role
Properties:
RoleName: HugoAWSCodeBuildServiceRole
Path: /
AssumeRolePolicyDocument: |
{
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": [ "codebuild.amazonaws.com" ]},
"Action": [ "sts:AssumeRole" ]
}]
}
Policies:
- PolicyName: root
PolicyDocument:
Version: 2012-10-17
Statement:
- Resource: "*"
Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
- Resource:
- !Sub arn:aws:s3:::${BucketName}/*
- !Sub arn:aws:s3:::${BucketName}
Effect: Allow
Action:
- s3:*
- Resource:
- !Sub arn:aws:s3:::${BuildBucket}/*
- !Sub arn:aws:s3:::${BuildBucket}
Effect: Allow
Action:
- s3:Get*
CodeBuild:
Type: AWS::CodeBuild::Project
Properties:
Artifacts:
Type: "CODEPIPELINE"
Source:
Type: "CODEPIPELINE"
Environment:
ComputeType: "BUILD_GENERAL1_SMALL"
Image: "node:8-alpine"
Type: "LINUX_CONTAINER"
EnvironmentVariables:
- Name: AWS_DEFAULT_REGION
Value: !Ref AWS::Region
- Name: BUCKET_NAME
Value: !Ref BucketName
- Name: CACHE_CONTROL_MAX_AGE
Value: !Ref CacheControlMaxAge
- Name: BRANCH
Value: !Ref GitHubBranch
- Name: REPO
Value: !Ref GitHubRepo
- Name: USER
Value: !Ref GitHubUser
Name: !Ref GitHubRepo
ServiceRole: !GetAtt CodeBuildServiceRole.Arn
Le buildspec
Plusieurs phases sont définies :
- L'installation de l'environnement
- La copie de fichiers
- La génération du site statique
- La copie des pages générées à la dernière étape sur le bucket S3 dédié
version: 0.2
phases:
install:
commands:
- apk update
- apk add ca-certificates wget
- update-ca-certificates
- apk add --no-cache --update python3
- pip3 install awscli
pre_build:
commands:
- wget https://github.com/gohugoio/hugo/releases/download/v0.40.3/hugo_0.40.3_Linux-64bit.tar.gz
- tar -xvzf hugo_0.40.3_Linux-64bit.tar.gz
- mv hugo /usr/bin/
- rm -f hugo_0.40.3_Linux-64bit.tar.gz
build:
commands:
- cd .. && mkdir blog_osones
- hugo new site blog_osones/
- cp -r src/* blog_osones/
- cd blog_osones
- hugo
post_build:
commands:
- aws s3 sync ./public s3://$BUCKET_NAME/ --delete --cache-control max-age=$CACHE_CONTROL_MAX_AGE
- AWS CodePipeline
La déclaration du CodePipeline est aussi précédée d'un Service Role permettant au service d'avoir accès aux ressources nécessaires, notamment le bucket S3 de stockage des artifacts. Les différentes étapes du pipeline sont ensuite définies. La source qui correspond au dépôt GitHub est récupérée et constitue l'artifact de sortie de l'étape. Dans la partie build, cet artifact est transmis comme input au CodeBuild précédemment déclaré.
CodePipelineServiceRole:
Type: AWS::IAM::Role
Properties:
RoleName: HugoAWSCodePipelineServiceRole
Path: /
AssumeRolePolicyDocument: |
{
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": [ "codepipeline.amazonaws.com" ]},
"Action": [ "sts:AssumeRole" ]
}]
}
Policies:
- PolicyName: root
PolicyDocument:
Version: 2012-10-17
Statement:
- Resource:
- !Sub arn:aws:s3:::${BuildBucket}/*
- !Sub arn:aws:s3:::${BuildBucket}
Effect: Allow
Action:
- s3:PutObject
- s3:GetObject
- s3:GetObjectVersion
- s3:GetBucketVersioning
- Resource: "*"
Effect: Allow
Action:
- codebuild:StartBuild
- codebuild:BatchGetBuilds
CodePipeline:
Type: AWS::CodePipeline::Pipeline
Properties:
Name: !Ref GitHubRepo
RoleArn: !GetAtt CodePipelineServiceRole.Arn
ArtifactStore:
Type: S3
Location: !Ref BuildBucket
Stages:
- Name: "Source"
Actions:
- Name: "Download_Source"
ActionTypeId:
Category: Source
Owner: ThirdParty
Version: 1
Provider: GitHub
Configuration:
Owner: !Ref GitHubUser
Repo: !Ref GitHubRepo
Branch: !Ref GitHubBranch
OAuthToken: !Ref GitHubToken
OutputArtifacts:
- Name: source
RunOrder: 1
- Name: "Build"
Actions:
- Name: "Build_and_deploy"
ActionTypeId:
Category: Build
Owner: AWS
Version: 1
Provider: CodeBuild
Configuration:
ProjectName: !Ref CodeBuild
InputArtifacts:
- Name: source
OutputArtifacts:
- Name: build
RunOrder: 1
- AWS CloudFront
On déclare ensuite une distribution Amazon CloudFront qui pointe sur le endpoint du bucket S3. On lui fournit aussi l'ARN du certificat généré dans les prérequis.
CloudFront:
Type: 'AWS::CloudFront::Distribution'
Properties:
DistributionConfig:
Origins:
- DomainName: !Select [2, !Split ["/", !GetAtt S3WebsiteBucket.WebsiteURL]]
Id: S3Website
CustomOriginConfig:
OriginProtocolPolicy: http-only
Enabled: true
HttpVersion: 'http2'
DefaultRootObject: index.html
Aliases:
- !Join ['', ['hugoblog.', !Ref DomainName]]
DefaultCacheBehavior:
AllowedMethods: ["HEAD", "GET"]
Compress: false
TargetOriginId: S3Website
ForwardedValues:
QueryString: 'true'
ViewerProtocolPolicy: redirect-to-https
PriceClass: PriceClass_All
ViewerCertificate:
AcmCertificateArn: !Ref AcmCertificateArn
SslSupportMethod: sni-only
- AWS Route 53
Enfin, nous ajoutons des RecordSet IPv4 et IPv6 Amazon Route 53 qui est en fait un enregistrement DNS qui définit un alias vers la distribution Cloudfront.
DNSRecordSetIPv4
AliasTarget:
DNSName: !GetAtt CloudfrontDistribution.DomainName
HostedZoneId: Z2FDTNDATAQYW2
Comment: Recordset to the cloudfront distribution
HostedZoneName: !Join ['',[!Ref DomainName,'.']]
Name: !Join ['', ['hugoblog.', !Ref DomainName]]
Type: A
DNSRecordSetIPv6
AliasTarget:
DNSName: !GetAtt CloudfrontDistribution.DomainName
HostedZoneId: Z2FDTNDATAQYW2
Comment: Recordset to the cloudfront distribution
HostedZoneName: !Join ['',[!Ref DomainName,'.']]
Name: !Join ['', ['hugoblog.', !Ref DomainName]]
Type: AAAA
Marche à suivre
- Créer votre "fork" du repo git
Pour avoir votre propre copie du projet dans votre repo Git. Le repository se trouve ici.
- Adapter le fichier de paramètres
Remplacer les valeurs par celles qui vous correspondent.
- Déployer le template CloudFormation
Et voilà !! Vous avez tout ce qu'il faut pour lancer votre blog en serverless sur AWS avec une mise en ligne automatique des nouveaux articles.
Didier NANDIEGOU
Découvrez les derniers articles d'alter way
- kubevpn
- Kubernetes 1.32
- re:Invent 2024 : AWS mise tout sur l'IA pour son Cloud
- OVHcloud Summit 2024 : L’innovation au cœur d’un cloud souverain et performant
- Big Data & AI Paris 2024 : L'IA au cœur de toutes les transformations.
- Un retour vers l'open-source ? Vos outils DevOps préférés et leurs equivalents open-source.