Ajouter une ressource au scoring
1️⃣ Identifier les événements pertinents
☑ Identifier la ressource à ajouter au score (exemple : une instance EC2, une Lambda, une instance RDS).
☑ Identifier l'événement associé à la création de la ressource.
- Exemple: dans le cas d'une instance EC2 il s'agit de l'événement nommé RunInstances.
Comment trouver les événements pertinents ?
Via la documentation officielle :
Documentation officielle AWS des events
En expérimentant :
- Créer une règle dans CloudWatch et sélectionner le service associé à la ressource. Pour l'event type, il est recommandé de choisir AWS API Call Via CloudTrail, cette option permet de capturer un événement avec plus de détails (notamment sur la partie IAM).
- Rediriger les events vers SNS par exemple, pour pouvoir les recevoir par mail lors de cette phase de test.
- Simuler le scénario souhaité : dans notre exemple nous créons une instance EC2 puis nous l'arrêtons.
- Les events vous seront envoyés presque instantanément. Il s'agit de documents json de ce type :
// JSON Simplifié généré lorsqu'un événement est capturé :
{
"detail-type": "AWS API Call via CloudTrail",
"source": "aws.ec2",
"account": "448878779811",
"time": "2020-03-30T12:39:03Z",
"detail": {
"eventSource": "ec2.amazonaws.com",
"eventName": "RunInstances",
"requestParameters": {
//...
},
"responseElements": {
//...
}
}
}
- Dans ce cas, nous pouvons déterminer que le nom de l'événement associé à la création d'une EC2 (l'attribut eventName dans le JSON de l'événement) est RunInstances.
Modifier l'event pattern utilisé par la règle du GameDay
☑ Modifier l'event pattern dans le code terraform permettant de mettre en place l'architecture du jeu :
- /gameday/gameday-infra/init/stage/service/cloudwatch/terragrunt.hcl
- rajouter au pattern les valeurs eventSource et eventName que l'application doit désormais traquer :
rule-event_pattern = <<PATTERN
{
"detail-type": [
"AWS API Call via CloudTrail"
],
"detail": {
"eventSource": [
"autoscaling.amazonaws.com",
# ... other eventSources
# <ADD HERE YOUR NEW EVENT SOURCE>
],
"eventName": [
"RunInstances",
"TerminateInstances",
# ... other eventNames
# <ADD HERE YOUR NEW EVENT NAME>
]
}
}
PATTERN
}
2️⃣ Ajouter la logique de scoring à la code base
Nous sommes désormais dans le repo gameday-master du gameday.
Définition de la ressource
Chaque resource doit être décrite par une classe implémentant l'interface Resource
. Cette interface mets à disposition une méthode computeCost
qui doit décrire comment calculer le cout de la ressource.
Pour certaines ressources le calcul s'effectue de manière très similaire donc certaines classes abstraites ont été mises en places pour faciliter le dévellopement (cf cas particulier)
export interface Resource {
RESOURCE_NAME: string;
START_EVENT_NAME: string;
END_EVENT_NAME: string;
COST: number;
computeCost(events: EventsGameday): number;
}
Une fois le fichier propre à la ressource créé (admin-server\src\scores\infrastructure\<PROVIDER>\<NOM DE VOTRE RESSOURCE>_resource.ts
.) avec son implémentation de calcul du cout il suffit d'ajouter la ressource dans le tableau les contenant toutes pour qu'elle soit prise en compte.
export const RESOURCES: Resource[] = [
EC2_RESOURCE,
LOADBALANCER_RESOURCE,
RDS_RESOURCE,
];
Cas : calcul à partir des événements de création et de terminaison :
Dans le cas où l'on dispose d'un évènement clair de début et de fin pour une ressource une classe abstraite facilitant le calcul du cout est mis à disposition : ResourceById
.
Il suffit donc pour votre ressource d'implémenter cette classe et de définir les deux méthodes abstraites : getIdsFromStartEvent
et getIdsFromEndEvent
. Ces méthodes décrivent simplement comment récupérer l'identifiant de la ressource dans l'event de création et dans l'event de terminaison. La méthode computeCost
de ResourceById se chargera du reste.
export abstract class ResourceById implements Resource {
abstract RESOURCE_NAME: string;
abstract START_EVENT_NAME: string;
abstract END_EVENT_NAME: string;
abstract COST: number;
abstract getIdsFromStartEvent(event: Event): string[];
abstract getIdsFromEndEvent(event: Event): string[];
computeCost(events: EventsGameday): number {
let usageTime = 0;
const byResourceEvents =
events[this.RESOURCE_NAME]?.[this.START_EVENT_NAME];
if (!byResourceEvents) {
return usageTime;
}
const startEvents = Object.values(byResourceEvents);
for (const eventStart of startEvents) {
const ids = this.getIdsFromStartEvent(eventStart);
ids.forEach((id) => {
const eventEnd = this.getEndEventFromId(events, id);
usageTime += getEventsDuration({ eventStart, eventEnd });
});
}
return usageTime * this.COST;
}
getEndEventFromId(events: EventsGameday, id: string): Event {
const endEvents = events[this.RESOURCE_NAME]?.[this.END_EVENT_NAME];
if (!endEvents) {
return undefined;
}
return Object.values(endEvents).find((event: Event) =>
this.getIdsFromEndEvent(event).includes(id)
);
}
}
Cas : calcul à partir d'une requête CloudWatch Logs Insights
A vérifier
☑ Parfois la ressource à monitorer ne produit pas les événements souhaités (exemple: dans le cas d'une lambda, aucun événement n'est produit lors de son appel).
☑ Dans ce cas, on peut utiliser une requête CloudWatch logs Insights afin d'analyser les fichiers de logs produits par la ressource afin de pouvoir déterminer sa durée totale d'utilisation.
☑ Vous devez spécifier la fonction getIdsFromStartEvent
qui permet de récupérer l'identifiant utilisé pour la création du logs group lié à la ressource dans CloudWatch.
- Exemple : dans le cas d'une lambda, le log group de la lambda est :
/aws/lambda/<NOM DE LA LAMBDA>
. getIdsFromStartEvent
doit donc pouvoir extraire de l'événementCreateFunction
le nom de la lambda.
☑ Vous devez également spécifier la query
(que l'on pourra placer définir dans admin-server\src\cloudwatch\<NOM DE VOTRE RESSOURCE>.ts
) qui sera exécutée sur le log group /aws/<RESOURCE_NAME>/<RESOURCE_ID>
.
- Exemple : pour récupérer la somme des durées d'exécution d'une lambda, on peut utiliser la query suivante :
const QUERY_LAMBDA_SUM_BILLED_DURATION = `filter @type = "REPORT"
| stats sum(@billedDuration)`;
const score: Score = {
timestamp: new Date(),
up: team.up,
down: team.down,
[ResourceName.<NOM DE VOTRE RESOURCE>]: await computeResourceFromLogs({
team,
resource: RESOURCE_<NOM DE VOTRE RESOURCE>,
getIdsFromStartEvent: getResourceIdsFromCloudWatchEvent_Start,
query: "YOUR CLOUD WATCH LOGS INSIGHTS QUERY"
}),