
L'architecture orientée événements améliore l'efficacité des ressources utilisées, car elles ne sont mobilisées que lorsque c'est nécessaire. Il existe de nombreuses façons de le réaliser sans créer d'entités cloud supplémentaires comme des applications worker. Aujourd'hui, je ne vais pas parler de FaaS, mais des webhooks. Je vais montrer un exemple d'apprentissage du traitement des événements à l'aide de webhooks de stockage d'objets.
Quelques mots sur le stockage d'objets et les webhooks. Les stockages d'objets permettent de conserver des donnĂ©es dans le cloud sous forme d'objets, accessibles via S3 ou une autre API (selon l'implĂ©mentation) par HTTP/HTTPS. Les webhooks, en gĂ©nĂ©ral, sont des appels de retour personnalisĂ©s par HTTP. Ils sont gĂ©nĂ©ralement dĂ©clenchĂ©s par un Ă©vĂ©nement, comme l'envoi de code dans un dĂ©pĂŽt ou un commentaire publiĂ© sur un blog. Lorsque l'Ă©vĂ©nement se produit, le site source envoie une requĂȘte HTTP Ă l'URL spĂ©cifiĂ©e pour le webhook. En consĂ©quence, il est possible de faire en sorte que des Ă©vĂ©nements sur un site entraĂźnent des actions sur un autre site. Dans le cas oĂč le site source est un stockage d'objets, les changements de son contenu jouent le rĂŽle d'Ă©vĂ©nements.). Dans le cas oĂč le site source est un stockage objet, les Ă©vĂ©nements sont les modifications de son contenu.
Exemples de cas simples oĂč l'on peut utiliser cette automatisation :
- CrĂ©ation de copies de tous les objets dans un autre stockage cloud. Les copies doivent ĂȘtre créées « Ă la volĂ©e », lors de l'ajout ou de la modification de fichiers.
- Création automatique de séries de miniatures de fichiers graphiques, ajout de filigranes sur des photos, autres modifications d'images.
- Notification de l'arrivée de nouveaux documents (par exemple, un service de comptabilité distribué met des rapports dans le cloud, et un service de surveillance financiÚre reçoit des notifications sur les nouveaux rapports, les vérifie et les analyse).
- Des cas lĂ©gĂšrement plus complexes impliquent, par exemple, la gĂ©nĂ©ration d'une requĂȘte Ă Kubernetes, qui crĂ©e un pod avec les conteneurs requis, y transfĂšre les paramĂštres de tĂąche et, aprĂšs traitement, ferme le conteneur.
Comme exemple, nous allons rĂ©aliser la tĂąche 1, oĂč les modifications dans le compartiment de stockage d'objets Mail.ru Cloud Solutions (MCS) sont synchronisĂ©es via des webhooks dans le stockage d'objets AWS. Dans un cas chargĂ© rĂ©el, il faudrait prĂ©voir un fonctionnement asynchrone en enregistrant les webhooks dans une file d'attente, mais pour cette tĂąche d'apprentissage, nous allons implĂ©menter sans cela.
Schéma de fonctionnement
Le protocole d'interaction est décrit en détail dans . Dans le schéma de fonctionnement, les éléments suivants sont présents :
- Service de publication, qui se trouve du cĂŽtĂ© du stockage S3 et publie des requĂȘtes HTTP lorsque le webhook se dĂ©clenche.
- Serveur de rĂ©ception des webhooks, qui Ă©coute les requĂȘtes du service de publication via HTTP et exĂ©cute les actions correspondantes. Le serveur peut ĂȘtre Ă©crit dans n'importe quel langage, dans notre exemple, nous allons Ă©crire le serveur en Go.
Une particularitĂ© de la mise en Ćuvre des webhooks dans l'API S3 est l'enregistrement du serveur de rĂ©ception des webhooks sur le service de publication. En particulier, le serveur de rĂ©ception des webhooks doit confirmer son abonnement aux messages du service de publication (dans d'autres mises en Ćuvre de webhooks, la confirmation de l'abonnement n'est gĂ©nĂ©ralement pas requise).
Par conséquent, le serveur de réception des webhooks doit prendre en charge deux opérations principales :
- rĂ©pondre Ă la requĂȘte du service de publication confirmant l'enregistrement,
- traiter les événements entrants.
Installation du serveur de réception des webhooks
Pour démarrer le serveur de réception des webhooks, un serveur Linux est nécessaire. Dans cet article, nous allons utiliser un exemple d'instance virtuelle déployée sur MCS.
Installons le logiciel nécessaire et lançons le serveur de réception des webhooks.
ubuntu@ubuntu-basic-1-2-10gb:~$ sudo apt-get install git
Lecture des listes de paquets... Fait
Construction de l'arbre des dépendances
Lecture des états d'information... Fait
Les paquets suivants ont été installés automatiquement et ne sont plus requis :
bc dns-root-data dnsmasq-base ebtables landscape-common liblxc-common
liblxc1 libuv1 lxcfs lxd lxd-client python3-attr python3-automat
python3-click python3-constantly python3-hyperlink
python3-incremental python3-pam python3-pyasn1-modules
python3-service-identity python3-twisted python3-twisted-bin
python3-zope.interface uidmap xdelta3
Utilisez 'sudo apt autoremove' pour les supprimer.
Paquets suggérés :
git-daemon-run | git-daemon-sysvinit git-doc git-el git-email git-gui
gitk gitweb git-cvs git-mediawiki git-svn
Les nouveaux paquets suivants seront installés :
git
0 mis à jour, 1 nouvellement installé, 0 à supprimer et 46 non mis à jour.
Besoin de récupérer 3915 kB d'archives.
AprÚs cette opération, 32.3 Mo d'espace disque supplémentaires seront utilisés.
Get:1 http://MS1.clouds.archive.ubuntu.com/ubuntu bionic-updates/main
amd64 git amd64 1:2.17.1-1ubuntu0.7 [3915 kB]
Récupéré 3915 kB en 1s (5639 kB/s)
Sélection du paquet git précédemment non sélectionné.
(Lecture de la base de données ... 53932 fichiers et répertoires actuellement installés.)
Préparation du dépaquetage .../git_12.17.1-1ubuntu0.7_amd64.deb ...
Dépaquetage de git (1:2.17.1-1ubuntu0.7) ...
Configuration de git (1:2.17.1-1ubuntu0.7) ...Clonons le dossier avec le serveur de réception des webhooks :
ubuntu@ubuntu-basic-1-2-10gb:~$ git clone
https://github.com/RomanenkoDenys/s3-webhook.git
Clonage dans 's3-webhook'...
remote: ĂnumĂ©ration des objets : 48, fait.
remote: Comptage des objets : 100 % (48/48), fait.
remote: Compression des objets : 100 % (27/27), fait.
remote: Total 114 (delta 20), réutilisé 45 (delta 18), pack-réutilisé 66
Réception des objets : 100 % (114/114), 23.77 MiB | 20.25 MiB/s, fait.
Résolution des deltas : 100 % (49/49), fait.Lançons le serveur :
ubuntu@ubuntu-basic-1-2-10gb:~$ cd s3-webhook/
ubuntu@ubuntu-basic-1-2-10gb:~/s3-webhook$ sudo ./s3-webhook -port 80Abonnement au service de publication
Vous pouvez enregistrer votre serveur de réception de webhooks via l'API ou l'interface web. Pour simplifier, nous allons enregistrer via l'interface web :
- dans le tableau de bord de gestion.
- Accédez au bucket pour lequel nous allons configurer les webhooks et cliquez sur l'icÎne en forme de roue dentée :

Allez Ă l'onglet Webhooks et cliquez sur Ajouter :

Remplissez les champs :

ID â nom du webhook.
ĂvĂ©nement â quels Ă©vĂ©nements transmettre. Nous avons configurĂ© la transmission de tous les Ă©vĂ©nements liĂ©s aux fichiers (ajouts et suppressions).
URL â adresse du serveur de rĂ©ception de webhooks.
Filtre prĂ©fixe/suffixe â filtre permettant de gĂ©nĂ©rer des webhooks uniquement pour les objets dont les noms rĂ©pondent Ă des critĂšres spĂ©cifiques. Par exemple, pour que le webhook ne s'active que pour les fichiers avec l'extension .png, dans filtre suffixe il faut Ă©crire «png».
Pour le moment, seuls les ports 80 et 443 sont pris en charge pour accéder au serveur de réception de webhooks.
Cliquons sur Ajouter hook et nous verrons ce qui suit :

Hook ajouté.
Le serveur de réception de webhooks montre dans les journaux le processus d'enregistrement du hook :
ubuntu@ubuntu-basic-1-2-10gb:~/s3-webhook$ sudo ./s3-webhook -port 80
2020/06/15 12:01:14 [POST] requĂȘte HTTP entrante de
95.163.216.92:42530
2020/06/15 12:01:14 Timestamp reçu : 2020-06-15T15:01:13+03:00 TopicArn :
mcs5259999770|myfiles-ash|s3:ObjectCreated:*,s3:ObjectRemoved:* Token :
E2itMqAMUVVZc51pUhFWSp13DoxezvRxkUh5P7LEuk1dEe9y URL :
http://89.208.199.220/webhook
2020/06/15 12:01:14 Signature de réponse générée :
3754ce36636f80dfd606c5254d64ecb2fd8d555c27962b70b4f759f32c76b66dEnregistrement terminé. Dans la section suivante, nous examinerons plus en détail l'algorithme de fonctionnement du serveur de réception de webhooks.
Description du serveur de réception de webhooks
Dans notre exemple, le serveur est écrit en Go. Examinons les principes de base de son fonctionnement.
package main
// Générer hmac_sha256_hex
func HmacSha256hex(message string, secret string) string {
}
// Générer hmac_sha256
func HmacSha256(message string, secret string) string {
}
// Envoyer confirmation d'abonnement
func SubscriptionConfirmation(w http.ResponseWriter, req *http.Request, body []byte) {
}
// Envoyer confirmation d'abonnement
func GotRecords(w http.ResponseWriter, req *http.Request, body []byte) {
}
// Liveness probe
func Ping(w http.ResponseWriter, req *http.Request) {
// log demande
log.Printf("[%s] requĂȘte HTTP Ping entrante de %sn", req.Method, req.RemoteAddr)
fmt.Fprintf(w, "Pongn")
}
//Webhook
func Webhook(w http.ResponseWriter, req *http.Request) {
}
func main() {
// obtenir les arguments de ligne de commande
bindPort := flag.Int("port", 80, "nombre entre 1-65535")
bindAddr := flag.String("address", "", "adresse IP au format point")
flag.StringVar(&actionScript, "script", "", "script externe à exécuter")
flag.Parse()
http.HandleFunc("/ping", Ping)
http.HandleFunc("/webhook", Webhook)
log.Fatal(http.ListenAndServe(*bindAddr+":"+strconv.Itoa(*bindPort), nil))
}Examinons les principales fonctionnalités :
- Ping() â route qui rĂ©pond Ă l'URL /ping, une implĂ©mentation simple de l'outil de vĂ©rification de disponibilitĂ©.
- Webhook() â route principale, gestionnaire de l'URL /webhook :
- confirme l'enregistrement sur le service de publication (passage Ă la fonction SubscriptionConfirmation),
- traiter les webhooks entrants (fonction Gotrecords).
- Les fonctions HmacSha256 et HmacSha256hex â implĂ©mentations des algorithmes de cryptage HMAC-SHA256 et HMAC-SHA256 avec sortie sous forme de chaĂźne de chiffres hexadĂ©cimaux pour le calcul de la signature.
- main â fonction principale, traite les paramĂštres de ligne de commande et enregistre les gestionnaires d'URL.
ParamÚtres de ligne de commande acceptés par le serveur :
- -port â le port sur lequel le serveur Ă©coutera.
- -address â l'adresse IP que le serveur Ă©coutera.
- -script â programme externe qui est appelĂ© pour chaque webhook reçu.
Examinons plus en détail certaines fonctions :
//Webhook
func Webhook(w http.ResponseWriter, req *http.Request) {
// Read body
body, err := ioutil.ReadAll(req.Body)
defer req.Body.Close()
if err != nil {
http.Error(w, err.Error(), 500)
return
}
// log request
log.Printf("[%s] incoming HTTP request from %sn", req.Method, req.RemoteAddr)
// check if we got subscription confirmation request
if strings.Contains(string(body),
""Type":"SubscriptionConfirmation"") {
SubscriptionConfirmation(w, req, body)
} else {
GotRecords(w, req, body)
}
}Cette fonction dĂ©termine ce qui est arrivĂ© â une demande de confirmation d'enregistrement ou un webhook. Comme le montre , en cas de confirmation d'enregistrement, la structure Json suivante arrive dans la requĂȘte Post :
POST http://test.com HTTP/1.1
x-amz-sns-messages-type: SubscriptionConfirmation
content-type: application/json
{
"Timestamp":"2019-12-26T19:29:12+03:00",
"Type":"SubscriptionConfirmation",
"Message":"Vous avez choisi de vous abonner au sujet $topic. Pour confirmer l'abonnement, vous devez répondre avec la signature calculée",
"TopicArn":"mcs2883541269|bucketA|s3:ObjectCreated:Put",
"SignatureVersion":1,
"Token":«RPE5UuG94rGgBH6kHXN9FUPugFxj1hs2aUQc99btJp3E49tA»
}Ă cette requĂȘte, il faut rĂ©pondre :
content-type: application/json
{"signature":«ea3fce4bb15c6de4fec365d36bcebbc34ccddf54616d5ca12e1972f82b6d37af»}OĂč la signature est calculĂ©e comme suit :
signature = hmac_sha256(url, hmac_sha256(TopicArn,
hmac_sha256(Timestamp, Token)))S'il s'agit d'un webhook, alors la structure de la requĂȘte Post ressemble Ă ceci :
POST HTTP/1.1
x-amz-sns-messages-type: SubscriptionConfirmation
{ "Records":
[
{
"s3": {
"object": {
"eTag":"aed563ecafb4bcc5654c597a421547b2",
"sequencer":1577453615,
"key":"some-file-to-bucket",
"size":100
},
"configurationId":"1",
"bucket": {
"name": "bucketA",
"ownerIdentity": {
"principalId":"mcs2883541269"}
},
"s3SchemaVersion":"1.0"
},
"eventVersion":"1.0",
"requestParameters":{
"sourceIPAddress":"185.6.245.156"
},
"userIdentity": {
"principalId":"2407013e-cbc1-415f-9102-16fb9bd6946b"
},
"eventName":"s3:ObjectCreated:Put",
"awsRegion":"ru-msk",
"eventSource":"aws:s3",
"responseElements": {
"x-amz-request-id":"VGJR5rtJ"
}
}
]
} Ainsi, en fonction de la requĂȘte, il faut comprendre comment traiter les donnĂ©es. J'ai choisi comme indicateur l'enregistrement "Type":"SubscriptionConfirmation", car elle est prĂ©sente dans la demande de confirmation d'abonnement et n'est pas prĂ©sente dans le webhook. Selon la prĂ©sence/absence de cet enregistrement dans la requĂȘte POST, l'exĂ©cution ultĂ©rieure du programme passe soit Ă la fonction SubscriptionConfirmation, soit Ă la fonction GotRecords.
Nous n'examinerons pas en dĂ©tail la fonction SubscriptionConfirmation, elle est implĂ©mentĂ©e selon les principes Ă©noncĂ©s dans . Le code source de cette fonction peut ĂȘtre consultĂ© dans le .
La fonction GotRecords analyse la requĂȘte entrante et pour chaque objet Record appelle un script externe (dont le nom a Ă©tĂ© fourni dans le paramĂštre -script) avec les paramĂštres :
- nom du bucket
- clé de l'objet
- action :
- copy â si dans la requĂȘte d'origine, EventName = ObjectCreated | PutObject | PutObjectCopy
- delete â si dans la requĂȘte d'origine, EventName = ObjectRemoved | DeleteObject
Ainsi, si un webhook arrive avec une requĂȘte POST, comme dĂ©crit , et que le paramĂštre -script=script.sh alors le script sera appelĂ© de la maniĂšre suivante :
script.sh bucketA some-file-to-bucket copyIl faut comprendre que ce serveur de réception de webhooks n'est pas une solution de production complÚte, mais un exemple simplifié d'une implémentation possible.
Exemple de fonctionnement
Nous allons synchroniser les fichiers du bucket principal dans MCS avec le bucket de sauvegarde dans AWS. Le bucket principal s'appelle myfiles-ash, le bucket de sauvegarde â myfiles-backup (la configuration du bucket dans AWS dĂ©passe le cadre de cet article). Ainsi, lorsque un fichier est placĂ© dans le bucket principal, sa copie doit apparaĂźtre dans le bucket de sauvegarde, et lorsqu'il est supprimĂ© du principal, il doit ĂȘtre supprimĂ© du sauvegarde.
Nous utiliserons l'outil awscli pour travailler avec les buckets, qui est compatible Ă la fois avec le stockage cloud MCS et le stockage cloud AWS.
ubuntu@ubuntu-basic-1-2-10gb:~$ sudo apt-get install awscli
Lecture des listes de paquets... Fait
Construction de l'arbre des dépendances
Lecture des informations d'état... Fait
AprÚs cette opération, 34,4 Mo d'espace disque supplémentaire seront utilisés.
Dépaquetage de awscli (1.14.44-1ubuntu1) ...
Configuration de awscli (1.14.44-1ubuntu1) ...Configurons l'accĂšs Ă l'API S3 MCS :
ubuntu@ubuntu-basic-1-2-10gb:~$ aws configure --profile mcs
AWS Access Key ID [None]: hdywEPtuuJTExxxxxxxxxxxxxx
AWS Secret Access Key [None]: hDz3SgxKwXoxxxxxxxxxxxxxxxxxx
Nom de la région par défaut [None]:
Format de sortie par défaut [None]:Configurons l'accÚs à l'API S3 AWS :
ubuntu@ubuntu-basic-1-2-10gb:~$ aws configure --profile aws
AWS Access Key ID [None]: AKIAJXXXXXXXXXXXX
AWS Secret Access Key [None]: dfuerphOLQwu0CreP5Z8l5fuXXXXXXXXXXXXXXXX
Nom de la région par défaut [None]:
Format de sortie par défaut [None]:Vérifions l'accÚs :
Ă AWS :
ubuntu@ubuntu-basic-1-2-10gb:~$ aws s3 ls --profile aws
2020-07-06 08:44:11 myfiles-backupPour MCS, lors de l'exĂ©cution de la commande, il faut ajouter âendpoint-url :
ubuntu@ubuntu-basic-1-2-10gb:~$ aws s3 ls --profile mcs --endpoint-url
https://hb.bizmrg.com
2020-02-04 06:38:05 databasebackups-0cdaaa6402d4424e9676c75a720afa85
2020-05-27 10:08:33 myfiles-ashAccĂšs obtenu.
Nous allons maintenant écrire un script pour traiter le webhook entrant, que nous appellerons s3_backup_mcs_aws.sh
#!/bin/bash
# Require aws cli
# if file added â copy it to backup bucket
# if file removed â remove it from backup bucket
# Variables
ENDPOINT_MCS="https://hb.bizmrg.com"
AWSCLI_MCS=`which aws`" --endpoint-url ${ENDPOINT_MCS} --profile mcs s3"
AWSCLI_AWS=`which aws`" --profile aws s3"
BACKUP_BUCKET="myfiles-backup"
SOURCE_BUCKET="${1}"
SOURCE_FILE="${2}"
ACTION="${3}"
SOURCE="s3://${SOURCE_BUCKET}/${SOURCE_FILE}"
TARGET="s3://${BACKUP_BUCKET}/${SOURCE_FILE}"
TEMP="/tmp/${SOURCE_BUCKET}/${SOURCE_FILE}"
case ${ACTION} in
"copy")
${AWSCLI_MCS} cp "${SOURCE}" "${TEMP}"
${AWSCLI_AWS} cp "${TEMP}" "${TARGET}"
rm ${TEMP}
;;
"delete")
${AWSCLI_AWS} rm ${TARGET}
;;
*)
echo "Usage: ${0} sourcebucket sourcefile copy/delete"
exit 1
;;
esacDémarrons le serveur :
ubuntu@ubuntu-basic-1-2-10gb:~\/s3-webhook$ sudo .\/s3-webhook -port 80 -
script scripts\/s3_backup_mcs_aws.shVĂ©rifions comment cela fonctionnera. Via nous allons ajouter le fichier test.txt dans le bucket myfiles-ash. Dans les logs de la console, on peut voir qu'une requĂȘte a Ă©tĂ© faite au serveur webhook :
2020\/07\/06 09:43:08 [POST] requĂȘte HTTP entrante de
95.163.216.92:56612
téléchargement : s3:\/\/myfiles-ash\/test.txt vers ..\/..\/..\/tmp\/myfiles-ash\/test.txt
upload : ..\/..\/..\/tmp\/myfiles-ash\/test.txt vers
s3:\/\/myfiles-backup\/test.txtVérifions le contenu du bucket myfiles-backup dans AWS :
ubuntu@ubuntu-basic-1-2-10gb:~\/s3-webhook$ aws s3 --profile aws ls
myfiles-backup
2020-07-06 09:43:10 1104 test.txtNous allons maintenant supprimer le fichier du bucket myfiles-ash via l'interface web.
Logs du serveur :
2020\/07\/06 09:44:46 [POST] requĂȘte HTTP entrante de
95.163.216.92:58224
délétion : s3:\/\/myfiles-backup\/test.txtContenu du bucket :
ubuntu@ubuntu-basic-1-2-10gb:~\/s3-webhook$ aws s3 --profile aws ls
myfiles-backup
ubuntu@ubuntu-basic-1-2-10gb:~$Le fichier a été supprimé, la tùche est accomplie.
Conclusion et ToDo
Tout le code utilisé dans cet article se trouve . Vous y trouverez également des exemples de scripts et des exemples de calculs de signatures pour l'enregistrement des webhooks.
Ce code n'est qu'un exemple de la façon dont vous pouvez utiliser les webhooks S3 dans votre activitĂ©. Comme je l'ai mentionnĂ© au dĂ©but, si vous prĂ©voyez d'utiliser un tel serveur en production, il est impĂ©ratif de réécrire le serveur pour un fonctionnement asynchrone : enregistrez les webhooks entrants dans une file d'attente (RabbitMQ ou NATS), puis traitez-les avec des applications worker. Sinon, lors d'une arrivĂ©e massive de webhooks, vous pourriez ĂȘtre confrontĂ© Ă un manque de ressources serveur pour exĂ©cuter les tĂąches. Disposer de files d'attente permet de rĂ©partir le serveur et les workers, et de gĂ©rer les questions de rĂ©exĂ©cution des tĂąches en cas d'Ă©checs. Il est Ă©galement souhaitable d'amĂ©liorer la journalisation en la rendant plus dĂ©taillĂ©e et plus standardisĂ©e.
Bonne chance !
à lire également sur le sujet :
Source : habr.com
