
Event-driven architectuur verhoogt de kosteneffectiviteit van de gebruikte middelen, omdat ze alleen worden ingezet wanneer ze nodig zijn. Er zijn talloze manieren om dit te implementeren zonder extra cloud-entiteiten te creƫren als worker-toepassingen. Vandaag zal ik niet over FaaS praten, maar over webhooks. Ik zal een leerzaam voorbeeld geven van het verwerken van evenementen met behulp van webhooks in objectopslag.
Even een paar woorden over objectopslag en webhooks. Objectopslag maakt het mogelijk om verschillende soorten gegevens in de cloud op te slaan als objecten die toegankelijk zijn via S3 of een andere API (afhankelijk van de implementatie) via HTTP/HTTPS. Webhooks zijn in het algemeen gebruikersspecifieke terugroepen via HTTP. Ze worden meestal geactiveerd door een gebeurtenis, zoals het verzenden van code naar een repository of een commentaar dat in een blog wordt gepubliceerd. Wanneer er een gebeurtenis plaatsvindt, stuurt de oorspronkelijke site een HTTP-verzoek naar de URL die voor de webhook is opgegeven. Dit maakt het mogelijk om gebeurtenissen op de ene site acties op een andere site te laten veroorzaken.). In het geval dat de oorspronkelijke site objectopslag is, zijn de gebeurtenissen wijzigingen in de inhoud ervan.
Voorbeelden van eenvoudige scenario's waarin deze automatisering kan worden gebruikt:
- Het maken van kopieƫn van alle objecten in een andere cloudopslag. Kopieƫn moeten 'on the fly' worden gemaakt bij elke toevoeging of wijziging van bestanden.
- Automatisch creƫren van een reeks miniaturen van grafische bestanden, het toevoegen van watermerken aan foto's, en andere beeldmodificaties.
- Melding van de komst van nieuwe documenten (bijvoorbeeld, een gedistribueerde boekhoudafdeling plaatst rapporten in de cloud, terwijl de financiƫle monitoring meldingen ontvangt van nieuwe rapporten, deze controleert en analyseert).
- Iets complexere scenario's omvatten bijvoorbeeld het vormen van een aanvraag aan Kubernetes, die een pod creƫert met de benodigde containers, de taakparameters doorgeeft en na verwerking de container opruimt.
Als voorbeeld zullen we versie 1 van de taak uitvoeren, waarbij wijzigingen in de bucket van de objectopslag Mail.ru Cloud Solutions (MCS) via webhooks worden gesynchroniseerd met de objectopslag van AWS. In een echte belastingscase moet men rekening houden met asynchrone werking door webhooks in een wachtrij te registreren, maar voor de leeropdracht zullen we de implementatie zonder dit maken.
Werking schema
Het communicatieprotocol is in detail beschreven in . In het werkingsschema zijn de volgende elementen aanwezig:
- Publicatiedienst, die zich aan de kant van de S3-opslag bevindt en HTTP-verzoeken publiceert wanneer de webhook wordt geactiveerd.
- Webhook-ontvangserver, die de verzoeken van de publicatiedienst via HTTP afluistert en de bijbehorende acties uitvoert. De server kan in elke taal worden geschreven; in ons voorbeeld zullen we de server in Go schrijven.
Een kenmerk van de implementatie van webhooks in de S3 API is de registratie van de webhook-ontvangserver bij de publicatiedienst. Concreet moet de webhook-ontvangserver de abonnementen op berichten van de publicatiedienst bevestigen (in andere implementaties van webhooks is een bevestiging van het abonnement meestal niet vereist).
Daarom moet de webhook-ontvangserver twee belangrijke operaties ondersteunen:
- beantwoorden op het verzoek van de publicatiedienst voor bevestiging van registratie,
- evenementen verwerken die binnenkomen.
Instelling van de webhook-ontvangserver
Voor het opzetten van de webhook-ontvangserver is een Linux-server nodig. In dit artikel gebruiken we als voorbeeld een virtuele instantie die we op MCS implementeren.
Laten we de benodigde software installeren en de webhook-ontvangserver opzetten.
ubuntu@ubuntu-basic-1-2-10gb:~$ sudo apt-get install git
Pakkettenlijsten uitlezen... Klaar
Afhankelijkheidsboom bouwen
Statusinformatie uitlezen... Klaar
De volgende pakketten zijn automatisch geĆÆnstalleerd en zijn niet langer nodig:
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
Gebruik 'sudo apt autoremove' om ze te verwijderen.
Suggestie pakketten:
git-daemon-run | git-daemon-sysvinit git-doc git-el git-email git-gui
gitk gitweb git-cvs git-mediawiki git-svn
De volgende NIEUWE pakketten worden geĆÆnstalleerd:
git
0 upgraded, 1 nieuw geïnstalleerd, 0 te verwijderen en 46 niet geüpgraded.
Er moet 3915 kB aan archieven worden gedownload.
Na deze operatie wordt er 32.3 MB aan extra schijfruimte gebruikt.
Haal op:1 http://MS1.clouds.archive.ubuntu.com/ubuntu bionic-updates/main
amd64 git amd64 1:2.17.1-1ubuntu0.7 [3915 kB]
Gedownload 3915 kB in 1s (5639 kB/s)
Kies het eerder niet geselecteerde pakket git.
(Database lezen ... 53932 bestanden en mappen momenteel geĆÆnstalleerd.)
Voorbereiding om uit te pakken .../git_12.17.1-1ubuntu0.7_amd64.deb ...
Uitpakken git (1:2.17.1-1ubuntu0.7) ...
Instellen van git (1:2.17.1-1ubuntu0.7) ...Het klonen van de map met de webhookserver:
ubuntu@ubuntu-basic-1-2-10gb:~$ git clone
https://github.com/RomanenkoDenys/s3-webhook.git
Klonen naar 's3-webhook'...
remote: Het aantal objecten tellen: 48, gereed.
remote: Tellen van objecten: 100% (48/48), gereed.
remote: Objecten comprimeren: 100% (27/27), gereed.
remote: Totaal 114 (delta 20), hergebruikt 45 (delta 18), pakket-hergebruikt 66
Ontvangen objecten: 100% (114/114), 23.77 MiB | 20.25 MiB/s, gereed.
Resolutie van deltas: 100% (49/49), gereed.Laten we de server starten:
ubuntu@ubuntu-basic-1-2-10gb:~$ cd s3-webhook/
ubuntu@ubuntu-basic-1-2-10gb:~/s3-webhook$ sudo ./s3-webhook -port 80Abonneren op de publicatieservice
Je kunt je webhook-server registreren via de API of de webinterface. Voor de eenvoud registreren we via de webinterface:
- in het beheerderspaneel.
- Ga naar de bucket waarvoor we webhooks gaan instellen en klik op het tandwiel:

Ga naar het tabblad Webhooks en klik op Toevoegen:

Vul de velden in:

ID ā de naam van de webhook.
Event ā welke gebeurtenissen door te geven. We hebben ingesteld dat alle gebeurtenissen die zich voordoen tijdens het werken met bestanden (toevoegen en verwijderen) worden doorgegeven.
URL ā het adres van de webhookserver.
Filter prefix/suffix ā een filter dat alleen webhooks genereert voor objecten waarvan de namen aan specifieke regels voldoen. Bijvoorbeeld, om ervoor te zorgen dat de webhook alleen afgaat voor bestanden met de extensie .png, moet je Filter suffix schrijven "png".
Momenteel worden alleen poorten 80 en 443 ondersteund voor toegang tot de webhookserver.
We klikken op Toevoegen hook en we zien het volgende:

Hook toegevoegd.
De webhookserver toont in de logs het proces van het registreren van de hook:
ubuntu@ubuntu-basic-1-2-10gb:~\/s3-webhook$ sudo .\/s3-webhook -port 80
2020\/06\/15 12:01:14 [POST] inkomend HTTP-verzoek van
95.163.216.92:42530
2020\/06\/15 12:01:14 Timestamp verkregen: 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 Genereer respons handtekening:
3754ce36636f80dfd606c5254d64ecb2fd8d555c27962b70b4f759f32c76b66dRegistratie voltooid. In het volgende gedeelte zullen we de werking van de webhook-ontvangserver gedetailleerder bekijken.
Beschrijving van de webhook-ontvangserver
In ons voorbeeld is de server geschreven in Go. Laten we de belangrijkste principes van de werking ervan bekijken.
package main
\/\/ Genereer hmac_sha256_hex
func HmacSha256hex(message string, secret string) string {
}
\/\/ Genereer hmac_sha256
func HmacSha256(message string, secret string) string {
}
\/\/ Verstuur abonnementsbevestiging
func SubscriptionConfirmation(w http.ResponseWriter, req *http.Request, body []byte) {
}
\/\/ Verstuur bevestiging van ontvangst
func GotRecords(w http.ResponseWriter, req *http.Request, body []byte) {
}
\/\/ Liveness probe
func Ping(w http.ResponseWriter, req *http.Request) {
\/\/ log verzoek
log.Printf("[%s] inkomend HTTP Ping verzoek van %sn", req.Method, req.RemoteAddr)
fmt.Fprintf(w, "Pongn")
}
\/\/Webhook
func Webhook(w http.ResponseWriter, req *http.Request) {
}
func main() {
\/\/ haal opdrachtregelargumenten op
bindPort := flag.Int("port", 80, "nummer tussen 1-65535")
bindAddr := flag.String("address", "", "ip-adres in puntnotatie")
flag.StringVar(&actionScript, "script", "", "externe script om uit te voeren")
flag.Parse()
http.HandleFunc("\/ping", Ping)
http.HandleFunc("\/webhook", Webhook)
log.Fatal(http.ListenAndServe(*bindAddr+":"+strconv.Itoa(*bindPort), nil))
}Laten we de belangrijkste functies bekijken:
- Ping() ā route die antwoord geeft op URL\/ping, de eenvoudigste implementatie van liveness probe.
- Webhook() ā de belangrijkste route, de handler voor URL\/webhook:
- bevestigt de registratie op de publicatieservice (overgang naar functie SubscriptionConfirmation),
- verwerkt binnenkomende webhooks (functie GotRecords).
- De functies HmacSha256 en HmacSha256hex zijn implementaties van de encryptie-algoritmen HMAC-SHA256 en HMAC-SHA256 met uitvoer in de vorm van hexadecimale getallen voor het berekenen van de handtekening.
- main ā de hoofd functie, verwerkt de opdrachtregelparameters en registreert de URL-handlers.
Opdrachtregelparameters die door de server worden geaccepteerd:
- -port ā de poort waar de server naar zal luisteren.
- -address ā het IP-adres dat de server zal beluisteren.
- -script ā extern programma dat wordt aangeroepen voor elke binnenkomende hook.
Laten we enkele functies nader bekijken:
//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)
}
}Deze functie bepaalt wat binnenkomt ā een aanvraag voor registratiebevestiging of een webhook. Zoals blijkt uit , in het geval van registratiebevestiging komt de volgende JSON-structuur in het POST-verzoek:
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":"Je hebt ervoor gekozen om je in te schrijven voor het onderwerp $topic. Om de inschrijving te bevestigen, moet je reageren met de berekende handtekening",
"TopicArn":"mcs2883541269|bucketA|s3:ObjectCreated:Put",
"SignatureVersion":1,
"Token":"RPE5UuG94rGgBH6kHXN9FUPugFxj1hs2aUQc99btJp3E49tA"
}Op deze aanvraag moet je reageren:
content-type: application/json
{"signature":"ea3fce4bb15c6de4fec365d36bcebbc34ccddf54616d5ca12e1972f82b6d37af"}Waarbij de handtekening wordt berekend als:
signature = hmac_sha256(url, hmac_sha256(TopicArn,
hmac_sha256(Timestamp, Token)))Als er een webhook binnenkomt, ziet de structuur van de Post-aanroep er als volgt uit:
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"
}
}
]
} Afhankelijk van de aanvraag moet je begrijpen hoe je de gegevens moet verwerken. Ik heb als indicator de record gekozen "Type":"SubscriptionConfirmation", aangezien deze aanwezig is in de bevestigingsaanvraag en niet aanwezig is in de webhook. Afhankelijk van de aanwezigheid/afwezigheid van deze record in de POST-aanroep, gaat de verdere uitvoering van het programma ofwel naar de functie SubscriptionConfirmation, of naar de functie GotRecords.
We zullen de functie SubscriptionConfirmation niet in detail bespreken, deze is gebaseerd op de principes die zijn uiteengezet in . Je kunt de broncode van deze functie vinden in .
De functie GotRecords verwerkt het binnenkomende verzoek en roept voor elk Record-object een extern script aan (de naam waarvan in de parameter -script is doorgegeven) met de parameters:
- de naam van de bucket
- de sleutel van het object
- handeling:
- copy ā als de EventName in de oorspronkelijke aanvraag = ObjectCreated | PutObject | PutObjectCopy
- delete ā als de EventName in de oorspronkelijke aanvraag = ObjectRemoved | DeleteObject
Dus, als er een hook aankomt met een Post-aanroep, zoals hierboven beschreven , en de parameter -script=script.sh, zal het script als volgt worden aangeroepen:
script.sh bucketA some-file-to-bucket copyHoud er rekening mee dat deze webhook-ontvangerserver geen kant-en-klare productie-oplossing is, maar een vereenvoudigd voorbeeld van een mogelijke implementatie.
Werkzaamheidsvoorbeeld
We zullen de bestanden van de hoofd-bucket in MCS synchroniseren met de reserve-bucket in AWS. De hoofd-bucket heet myfiles-ash en de reserve-bucket myfiles-backup (de configuratie van de bucket in AWS valt buiten de reikwijdte van dit artikel). Wanneer er dus een bestand in de hoofd-bucket wordt geplaatst, moet er een kopie in de reserve-bucket verschijnen, en wanneer het uit de hoofd-bucket wordt verwijderd, moet het ook in de reserve-bucket worden verwijderd.
We zullen werken met de utility awscli, die compatibel is met zowel de cloudopslag MCS als de cloudopslag AWS.
ubuntu@ubuntu-basic-1-2-10gb:~$ sudo apt-get install awscli
Lezende pakketlijsten... Voltooid
Afhankelijkheidsboom bouwen
Lezende statusinformatie... Voltooid
Na deze operatie zal er 34,4 MB extra schijfruimte worden gebruikt.
Awscli (1.14.44-1ubuntu1) uitpakken ...
Awscli (1.14.44-1ubuntu1) instellen ...Laten we de toegang tot de S3 MCS API configureren:
ubuntu@ubuntu-basic-1-2-10gb:~$ aws configure --profile mcs
AWS Access Key ID [None]: hdywEPtuuJTExxxxxxxxxxxxxx
AWS Secret Access Key [None]: hDz3SgxKwXoxxxxxxxxxxxxxxxxxx
Default region name [None]:
Default output format [None]:Laten we de toegang tot de S3 AWS API configureren:
ubuntu@ubuntu-basic-1-2-10gb:~$ aws configure --profile aws
AWS Access Key ID [None]: AKIAJXXXXXXXXXXXX
AWS Secret Access Key [None]: dfuerphOLQwu0CreP5Z8l5fuXXXXXXXXXXXXXXXX
Default region name [None]:
Default output format [None]:Laten we de toegangen controleren:
Naar AWS:
ubuntu@ubuntu-basic-1-2-10gb:~$ aws s3 ls --profile aws
2020-07-06 08:44:11 myfiles-backupVoor MCS moet je de āendpoint-url toevoegen aan het commando:
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-ashToegang verleend.
Laten we nu een script schrijven voor het verwerken van de binnenkomende hook, we noemen het 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
;;
esacWe starten de server:
ubuntu@ubuntu-basic-1-2-10gb:~/s3-webhook$ sudo ./s3-webhook -port 80 -
script scripts/s3_backup_mcs_aws.shLaten we controleren hoe dit zal werken. Via voegen we het bestand test.txt toe aan de bucket myfiles-ash. In de logs van de console is te zien dat er een verzoek is gedaan aan de webhookserver:
2020/07/06 09:43:08 [POST] binnenkomend HTTP-verzoek van
95.163.216.92:56612
download: s3://myfiles-ash/test.txt naar ../..../tmp/myfiles-ash/test.txt
upload: ../..../tmp/myfiles-ash/test.txt naar
s3://myfiles-backup/test.txtLaten we de inhoud van de bucket myfiles-backup in AWS controleren:
ubuntu@ubuntu-basic-1-2-10gb:~/s3-webhook$ aws s3 --profile aws ls
myfiles-backup
2020-07-06 09:43:10 1104 test.txtLaten we nu via de webinterface het bestand uit de bucket myfiles-ash verwijderen.
Serverlogs:
2020/07/06 09:44:46 [POST] binnenkomend HTTP-verzoek van
95.163.216.92:58224
delete: s3://myfiles-backup/test.txtInhoud van de bucket:
ubuntu@ubuntu-basic-1-2-10gb:~/s3-webhook$ aws s3 --profile aws ls
myfiles-backup
ubuntu@ubuntu-basic-1-2-10gb:~$Bestand is verwijderd, taak is voltooid.
Conclusie en ToDo
Alle code die in dit artikel wordt gebruikt, is te vinden . Hier vindt u voorbeelden van scripts en voorbeelden van het berekenen van handtekeningen voor het registreren van webhooks.
Deze code is niet meer dan een voorbeeld van hoe je S3-webhooks in je activiteiten kunt gebruiken. Zoals ik aan het begin zei, als je van plan bent om een dergelijke server in productie te gebruiken, moet je minstens de server herschrijven voor asynchrone werking: binnenkomende webhooks registreren in een wachtrij (RabbitMQ of NATS), en van daaruit deze te ontleden en te verwerken met worker-applicaties. Anders kun je bij massale binnenkomst van webhooks tegen een tekort aan serverbronnen voor het uitvoeren van taken aanlopen. Het gebruik van wachtrijen maakt het mogelijk om de server en workers te splitsen, en ook om problemen met het herhalen van taken bij storingen op te lossen. Het is ook wenselijk om de logging te veranderen naar een meer gedetailleerde en meer gestandaardiseerde vorm.
Veel succes!
Lees ook over dit onderwerp:
Bron: habr.com
