Symfony/Bref/Pulumi : ajout d'un nom de domaine et d'une tâche planifiée
CEO/CTO/CMO/Dev/Ops/Support at Panda Code
Préambule
Cette article est le deuxième d'une série sur le déploiement d'un projet Symfony sur AWS Lambda avec l'outil d'IaC Pulumi. Dans le premier article, nous avons mis en place le nécessaire pour avoir notre API accessible via une url fournie par AWS ressemblant à ça : https://0nz9kqsta9.execute-api.eu-west-3.amazonaws.com
Dans ce deuxième article, nous allons voir comment utiliser un nom de domaine plus friendly pour notre API et comment mettre en place une tâche planifiée (aka cron): 2 choses essentielles dans tout projet ;)
Comme pour le premier article, le code est disponible sur le repository Github, et j'ai fait en sorte qu'il y ait une PR contenant seulement les modifications liées à ce deuxième article.
La partie "Infrastructure" : le sous-domaine personnalisé
J'ai acheté le nom de domaine my-projects.tech spécialement pour écrire cet article : nous allons faire en sorte que le sous-domaine api.my-projects.tech soit utilisé pour notre API.
Le certificat SSL (AWS Certificate Manager)
Avant de pouvoir plugger notre domaine sur notre API Gateway, nous avons besoin d'un certificat SSL que nous obtiendrons en utilisant un service AWS : celui-ci sera ensuite facilement utilisable avec les autres services.
// src/acm/index.ts
import * as aws from "@pulumi/aws";
export const sslCertificateApi = new aws.acm.Certificate("api",
{
domainName: "api.my-projects.tech",
validationMethod: "DNS",
}
);
Le DNS (Amazon Route53)
Nous voyons que la méthode de validation du certificat choisie est DNS. Nous allons donc utiliser le service Route53 pour gérer les enregistrements DNS nécessaires à cette validation.
// src/route53/index.ts
import * as aws from "@pulumi/aws";
import {sslCertificateApi} from "../acm";
import {apiDomainName} from "../api-gateway";
// La liste des serveurs de nom de domaines
export const mainDelegationSet = new aws.route53.DelegationSet("main", {}, {});
// Création de la Zone principale (domaine)
export const zone = new aws.route53.Zone("my-projects", {
delegationSetId: mainDelegationSet.id,
name: "my-projects.tech",
});
// L'enregistrement DNS permettant la validation du certificat SSL
const apiAcmValidation = new aws.route53.Record("api.acm",
{
zoneId: zone.zoneId,
name: sslCertificateApi.domainValidationOptions[0].resourceRecordName,
type: sslCertificateApi.domainValidationOptions[0].resourceRecordType,
records: [sslCertificateApi.domainValidationOptions[0].resourceRecordValue],
ttl: 10 * 60,
}
);
// Lance la validation du certificat SSL
new aws.acm.CertificateValidation(
'api',
{
certificateArn: sslCertificateApi.arn,
validationRecordFqdns: [apiAcmValidation.fqdn],
}
);
Note : si vous n'avez pas acheté votre nom de domain chez AWS (ce qui est mon cas => chez Gandi), il faudra modifier les serveurs de noms pour définir ceux d'AWS. Pour connaître la liste, il faudra préalablement faire un
pulumi uppour créer la zone et récupérer la liste (DelegationSet dans le code).
Modification de notre API Gateway
Il nous restera une petite modification à faire dans Route53, mais nous avons d'abord besoin de modifier notre API Gateway pour prendre en compte notre sous-domaine et le mapper à notre stage.
// src/api-gateway/index.ts
import * as aws from "@pulumi/aws";
import {sslCertificateApi} from "../acm";
export const apiApi = new aws.apigatewayv2.Api("api", {
protocolType: "HTTP",
});
const apiStage = new aws.apigatewayv2.Stage("api.default", {
apiId: apiApi.id,
autoDeploy: true,
name: "$default"
});
// Ajout de notre sous-domaine personnalisé et utilisant de notre certificat SSL
export const apiDomainName = new aws.apigatewayv2.DomainName("api", {
domainName: 'api.my-projects.tech',
domainNameConfiguration: {
certificateArn: sslCertificateApi.arn,
endpointType: "REGIONAL",
securityPolicy: "TLS_1_2",
},
});
// Mapping de notre sous-domaine avec le stage de notre APIG
new aws.apigatewayv2.ApiMapping("api.api", {
apiId: apiApi.id,
domainName: apiDomainName.id,
stage: apiStage.id
});
Il ne manque plus qu'à créer l'enregistrement DNS pour que le sous-domaine api.my-projects.tech pointe bien vers notre API Gateway.
// src/route53/index.ts
// ...
// Création de l'enregistrement DNS qui utilise les informations du nom de domaine défini dans API Gateway (aws.apigatewayv2.DomainName).
new aws.route53.Record('api.apig', {
zoneId: zone.zoneId,
name: 'api.my-projects.tech',
type: "A",
aliases: [{
evaluateTargetHealth: true,
name: apiDomainName.domainNameConfiguration.targetDomainName,
zoneId: apiDomainName.domainNameConfiguration.hostedZoneId
}],
});
Il n'y a plus qu'à pulumi up et notre API sera accessible via notre sous-domaine personnalisé.

La partie "code" : la tâche planifiée
Cette partie est très simple car gérée par Serverless framework (doc Schedule) et Bref (doc Cron). Il suffit de modifier le fichier serverless.yml pour configurer l'exécution de notre cron.
// serverless.yml
cron1:
handler: bin/console
layers:
- ${bref:layer.php-82}
- ${bref:layer.console}
events:
- schedule:
enabled: true
rate: cron(*/15 * * * ? *) # l'expression de planification (https://docs.aws.amazon.com/AmazonCloudWatch/latest/events/ScheduledEvents.html)
input: '"app:cron1 arg1 --option1=foo"' # la commande Symfony
Voici un petit extrait des dernières invocations que l'on peut trouver sur la Console Web sur la page de détails de la fonction Lambda (pour les plus observateurs, la planification n'a pas toujours été toutes les 15 minutes ^^).

C'est fini pour aujourd'hui !
Dans le prochain article, nous verrons comment ajouter un serveur SQL (mais serverless quand même ^^) à notre infrastructure et comment faire en sorte que nos Lambda en Symfony y accèdent.
N'hésitez pas à commenter ce post, ou me laisser un message sur Twitter/Linkedin pour toutes remarques de tout type (erreur, avis, envie, question, ...) : j'essaierai d'en tenir compte et d'y répondre rapidement.
