Een voorbeeld van een event-driven applicatie gebaseerd op webhooks in S3-objectopslag Mail.ru Cloud Solutions

Een voorbeeld van een event-driven applicatie gebaseerd op webhooks in S3-objectopslag Mail.ru Cloud Solutions
Rube Goldberg koffiezetapparaat

Event-driven architectuur verhoogt de kostenefficiëntie van de gebruikte middelen, omdat ze alleen worden gebruikt op het moment dat ze nodig zijn. Er zijn veel opties om dit te implementeren en geen extra cloudentiteiten als werktoepassingen te maken. En vandaag zal ik het niet over FaaS hebben, maar over webhooks. Ik zal een tutorialvoorbeeld laten zien van het afhandelen van gebeurtenissen met behulp van webhooks voor objectopslag.

Een paar woorden over objectopslag en webhooks. Met objectopslag kunt u alle gegevens in de cloud opslaan in de vorm van objecten, toegankelijk via S3 of een andere API (afhankelijk van de implementatie) via HTTP/HTTPS. Webhooks zijn over het algemeen aangepaste HTTP-callbacks. Ze worden doorgaans geactiveerd door een gebeurtenis, zoals het pushen van code naar een opslagplaats of het plaatsen van een opmerking op een blog. Wanneer er een gebeurtenis plaatsvindt, verzendt de oorspronkelijke site een HTTP-verzoek naar de URL die is opgegeven voor de webhook. Als gevolg hiervan kunt u ervoor zorgen dat gebeurtenissen op de ene site acties op een andere site activeren (wiki). In het geval dat de bronsite een objectopslag is, fungeren gebeurtenissen als wijzigingen in de inhoud ervan.

Voorbeelden van eenvoudige gevallen waarin dergelijke automatisering kan worden gebruikt:

  1. Kopieën maken van alle objecten in een andere cloudopslag. Kopieën moeten direct worden gemaakt wanneer bestanden worden toegevoegd of gewijzigd.
  2. Automatische creatie van een reeks miniaturen van grafische bestanden, het toevoegen van watermerken aan foto's en andere beeldaanpassingen.
  3. Melding over de komst van nieuwe documenten (een gedistribueerde boekhouddienst uploadt bijvoorbeeld rapporten naar de cloud en financiële monitoring ontvangt meldingen over nieuwe rapporten, controleert en analyseert deze).
  4. Iets complexere gevallen omvatten bijvoorbeeld het genereren van een verzoek aan Kubernetes, dat een pod met de benodigde containers maakt, taakparameters eraan doorgeeft en na verwerking de container samenvouwt.

Als voorbeeld maken we een variant van taak 1, waarbij wijzigingen in de Mail.ru Cloud Solutions (MCS) objectopslagbucket worden gesynchroniseerd in AWS-objectopslag met behulp van webhooks. In een echt belast geval zou asynchroon werk geleverd moeten worden door webhooks in een wachtrij te registreren, maar voor de trainingstaak zullen we de implementatie zonder dit doen.

Schema van werk

Het interactieprotocol wordt gedetailleerd beschreven in Gids voor S3-webhooks op MCS. Het werkschema bevat de volgende elementen:

  • Uitgavedienst, dat zich aan de S3-opslagkant bevindt en HTTP-verzoeken publiceert wanneer de webnhook wordt geactiveerd.
  • Webhook-ontvangstserver, dat luistert naar verzoeken van de HTTP-publicatieservice en passende acties uitvoert. De server kan in elke taal worden geschreven; in ons voorbeeld schrijven we de server in Go.

Een speciaal kenmerk van de implementatie van webhooks in de S3 API is de registratie van de webhook-ontvangende server op de publicatieservice. In het bijzonder moet de webhook-ontvangende server het abonnement op berichten van de publicatieservice bevestigen (bij andere webhook-implementaties is bevestiging van het abonnement meestal niet vereist).

Dienovereenkomstig moet de webhook-ontvangende server twee hoofdbewerkingen ondersteunen:

  • reageren op het verzoek van de publicatiedienst om de registratie te bevestigen,
  • binnenkomende gebeurtenissen verwerken.

Een webhook-ontvangstserver installeren

Om de webhook-ontvangstserver te kunnen gebruiken, hebt u een Linux-server nodig. In dit artikel gebruiken we als voorbeeld een virtuele instance die we op MCS implementeren.

Laten we de benodigde software installeren en de webhook-ontvangstserver starten.

ubuntu@ubuntu-basic-1-2-10gb:~$ sudo apt-get install git
Reading package lists... Done
Building dependency tree
Reading state information... Done
The following packages were automatically installed and are no longer required:
  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
Use 'sudo apt autoremove' to remove them.
Suggested packages:
  git-daemon-run | git-daemon-sysvinit git-doc git-el git-email git-gui 
gitk gitweb git-cvs git-mediawiki git-svn
The following NEW packages will be installed:
  git
0 upgraded, 1 newly installed, 0 to remove and 46 not upgraded.
Need to get 3915 kB of archives.
After this operation, 32.3 MB of additional disk space will be used.
Get:1 http://MS1.clouds.archive.ubuntu.com/ubuntu bionic-updates/main 
amd64 git amd64 1:2.17.1-1ubuntu0.7 [3915 kB]
Fetched 3915 kB in 1s (5639 kB/s)
Selecting previously unselected package git.
(Reading database ... 53932 files and directories currently installed.)
Preparing to unpack .../git_1%3a2.17.1-1ubuntu0.7_amd64.deb ...
Unpacking git (1:2.17.1-1ubuntu0.7) ...
Setting up git (1:2.17.1-1ubuntu0.7) ...

Kloon de map met de webhook-ontvangende server:

ubuntu@ubuntu-basic-1-2-10gb:~$ git clone
https://github.com/RomanenkoDenys/s3-webhook.git
Cloning into 's3-webhook'...
remote: Enumerating objects: 48, done.
remote: Counting objects: 100% (48/48), done.
remote: Compressing objects: 100% (27/27), done.
remote: Total 114 (delta 20), reused 45 (delta 18), pack-reused 66
Receiving objects: 100% (114/114), 23.77 MiB | 20.25 MiB/s, done.
Resolving deltas: 100% (49/49), done.

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 80

Abonneer u op de publicatieservice

U kunt uw webhook-ontvangende server registreren via de API of webinterface. Voor de eenvoud zullen we ons registreren via de webinterface:

  1. Laten we naar het gedeelte met emmers gaan in de controlekamer.
  2. Ga naar de bucket waarvoor we webhooks gaan configureren en klik op het tandwiel:

Een voorbeeld van een event-driven applicatie gebaseerd op webhooks in S3-objectopslag Mail.ru Cloud Solutions

Ga naar het tabblad Webhooks en klik op Toevoegen:

Een voorbeeld van een event-driven applicatie gebaseerd op webhooks in S3-objectopslag Mail.ru Cloud Solutions
Vul de velden in:

Een voorbeeld van een event-driven applicatie gebaseerd op webhooks in S3-objectopslag Mail.ru Cloud Solutions

ID — de naam van de webhook.

Gebeurtenis - welke gebeurtenissen moeten worden verzonden. We hebben de overdracht ingesteld van alle gebeurtenissen die optreden bij het werken met bestanden (toevoegen en verwijderen).

URL — webhook die het serveradres ontvangt.

Filtervoorvoegsel/achtervoegsel is een filter waarmee u alleen webhooks kunt genereren voor objecten waarvan de namen aan bepaalde regels voldoen. Als u er bijvoorbeeld voor wilt zorgen dat de webhook alleen bestanden met de extensie .png activeert, moet u in Filterachtervoegsel je moet "png" schrijven.

Momenteel worden alleen poorten 80 en 443 ondersteund voor toegang tot de webhook-ontvangende server.

Laten we klikken Haak toevoegen en we zullen het volgende zien:

Een voorbeeld van een event-driven applicatie gebaseerd op webhooks in S3-objectopslag Mail.ru Cloud Solutions
Haak toegevoegd.

De webhook-ontvangende server toont in zijn logboeken de voortgang van het hook-registratieproces:

ubuntu@ubuntu-basic-1-2-10gb:~/s3-webhook$ sudo ./s3-webhook -port 80
2020/06/15 12:01:14 [POST] incoming HTTP request from 
95.163.216.92:42530
2020/06/15 12:01:14 Got timestamp: 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 Generate responce signature: 
3754ce36636f80dfd606c5254d64ecb2fd8d555c27962b70b4f759f32c76b66d

De registratie is voltooid. In de volgende sectie zullen we het werkingsalgoritme van de webhook-ontvangende server nader bekijken.

Beschrijving van de webhook-ontvangende server

In ons voorbeeld is de server geschreven in Go. Laten we eens kijken naar de basisprincipes van de werking ervan.

package main

// Generate hmac_sha256_hex
func HmacSha256hex(message string, secret string) string {
}

// Generate hmac_sha256
func HmacSha256(message string, secret string) string {
}

// Send subscription confirmation
func SubscriptionConfirmation(w http.ResponseWriter, req *http.Request, body []byte) {
}

// Send subscription confirmation
func GotRecords(w http.ResponseWriter, req *http.Request, body []byte) {
}

// Liveness probe
func Ping(w http.ResponseWriter, req *http.Request) {
    // log request
    log.Printf("[%s] incoming HTTP Ping request from %sn", req.Method, req.RemoteAddr)
    fmt.Fprintf(w, "Pongn")
}

//Webhook
func Webhook(w http.ResponseWriter, req *http.Request) {
}

func main() {

    // get command line args
    bindPort := flag.Int("port", 80, "number between 1-65535")
    bindAddr := flag.String("address", "", "ip address in dot format")
    flag.StringVar(&actionScript, "script", "", "external script to execute")
    flag.Parse()

    http.HandleFunc("/ping", Ping)
    http.HandleFunc("/webhook", Webhook)

log.Fatal(http.ListenAndServe(*bindAddr+":"+strconv.Itoa(*bindPort), nil))
}

Overweeg de belangrijkste functies:

  • Ping() - een route die reageert via URL/ping, de eenvoudigste implementatie van een liveness-sonde.
  • Webhook() - hoofdroute, URL/webhook-handler:
    • bevestigt de registratie bij de publicatieservice (ga naar de functie SubscriptionConfirmation),
    • verwerkt inkomende webhooks (Gorecords-functie).
  • Functies HmacSha256 en HmacSha256hex zijn implementaties van de HMAC-SHA256- en HMAC-SHA256-coderingsalgoritmen met uitvoer als een reeks hexadecimale getallen voor het berekenen van de handtekening.
  • main is de hoofdfunctie, verwerkt opdrachtregelparameters en registreert URL-handlers.

Door de server geaccepteerde opdrachtregelparameters:

  • -port is de poort waarop de server zal luisteren.
  • -adres - IP-adres waarnaar de server zal luisteren.
  • -script is een extern programma dat voor elke inkomende hook wordt aangeroepen.

Laten we enkele functies eens 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 of er een verzoek om registratie te bevestigen of een webhook is binnengekomen. Zoals volgt uit documentatieAls de registratie is bevestigd, wordt de volgende JSON-structuur ontvangen in de Post-aanvraag:

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":"You have chosen to subscribe to the topic $topic. To confirm the subscription you need to response with calculated signature",
    "TopicArn":"mcs2883541269|bucketA|s3:ObjectCreated:Put",
    "SignatureVersion":1,
    "Token":«RPE5UuG94rGgBH6kHXN9FUPugFxj1hs2aUQc99btJp3E49tA»
}

Deze vraag moet beantwoord worden:

content-type: application/json

{"signature":«ea3fce4bb15c6de4fec365d36bcebbc34ccddf54616d5ca12e1972f82b6d37af»}

Waar 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 het Post-verzoek er als volgt uit:

POST <url> 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 het verzoek moet u daarom begrijpen hoe u de gegevens moet verwerken. Ik heb de vermelding als indicator gekozen "Type":"SubscriptionConfirmation", omdat deze aanwezig is in het bevestigingsverzoek voor het abonnement en niet aanwezig is in de webhook. Op basis van de aan-/afwezigheid van deze invoer in het POST-verzoek gaat de verdere uitvoering van het programma naar de functie SubscriptionConfirmation, of in de functie GotRecords.

We zullen de functie Abonnementbevestiging niet in detail bespreken; deze wordt geïmplementeerd volgens de principes die zijn uiteengezet in documentatie. U kunt de broncode voor deze functie bekijken op project git-opslagplaatsen.

De GotRecords-functie ontleedt het binnenkomende verzoek en roept voor elk Record-object een extern script aan (waarvan de naam is doorgegeven in de parameter -script) met de parameters:

  • naam van de emmer
  • objectsleutel
  • actie:
    • copy - if in de oorspronkelijke aanvraag EventName = ObjectCreated | ZetObject | ZetObjectKopiëren
    • verwijderen - als in de oorspronkelijke aanvraag EventName = ObjectRemoved | Object verwijderen

Dus als er een hook arriveert met een Post-verzoek, zoals beschreven bovenen de parameter -script=script.sh, dan wordt het script als volgt aangeroepen:

script.sh  bucketA some-file-to-bucket copy

Het moet duidelijk zijn dat deze webhook-ontvangstserver geen volledige productieoplossing is, maar een vereenvoudigd voorbeeld van een mogelijke implementatie.

Voorbeeld van werk

Laten we de bestanden van de hoofdbucket in MCS synchroniseren met de back-upbucket in AWS. De hoofdbucket heet myfiles-ash, de back-up heet myfiles-backup (bucketconfiguratie in AWS valt buiten het bestek van dit artikel). Dienovereenkomstig moet, wanneer een bestand in de hoofdbucket wordt geplaatst, de kopie ervan in de back-up verschijnen, en wanneer het uit de hoofdbucket wordt verwijderd, moet het in de back-up worden verwijderd.

We zullen met buckets werken met behulp van het awscli-hulpprogramma, dat compatibel is met zowel MCS-cloudopslag als AWS-cloudopslag.

ubuntu@ubuntu-basic-1-2-10gb:~$ sudo apt-get install awscli
Reading package lists... Done
Building dependency tree
Reading state information... Done
After this operation, 34.4 MB of additional disk space will be used.
Unpacking awscli (1.14.44-1ubuntu1) ...
Setting up awscli (1.14.44-1ubuntu1) ...

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 AWS S3 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:

Aan AWS:

ubuntu@ubuntu-basic-1-2-10gb:~$ aws s3 ls --profile aws
2020-07-06 08:44:11 myfiles-backup

Voor MCS moet u bij het uitvoeren van de opdracht —endpoint-url toevoegen:

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-ash

Toegankelijk.

Laten we nu een script schrijven voor het verwerken van de inkomende hook, laten we het s3_backup_mcs_aws.sh noemen

#!/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=""
SOURCE_FILE=""
ACTION=""

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: 
#!/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
;;
esac
sourcebucket sourcefile copy/delete" exit 1 ;; esac

Laten we de server starten:

ubuntu@ubuntu-basic-1-2-10gb:~/s3-webhook$ sudo ./s3-webhook -port 80 -
script scripts/s3_backup_mcs_aws.sh

Laten we kijken hoe het werkt. Door MCS-webinterface voeg het bestand test.txt toe aan de myfiles-ash-bucket. Uit de consolelogboeken blijkt dat er een verzoek is ingediend bij de webhookserver:

2020/07/06 09:43:08 [POST] incoming HTTP request from 
95.163.216.92:56612
download: s3://myfiles-ash/test.txt to ../../../tmp/myfiles-ash/test.txt
upload: ../../../tmp/myfiles-ash/test.txt to 
s3://myfiles-backup/test.txt

Laten we de inhoud van de myfiles-backup-bucket 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.txt

Nu zullen we via de webinterface het bestand verwijderen uit de myfiles-ash-bucket.

Serverlogboeken:

2020/07/06 09:44:46 [POST] incoming HTTP request from 
95.163.216.92:58224
delete: s3://myfiles-backup/test.txt

Inhoud emmer:

ubuntu@ubuntu-basic-1-2-10gb:~/s3-webhook$ aws s3 --profile aws ls 
myfiles-backup
ubuntu@ubuntu-basic-1-2-10gb:~$

Het bestand wordt verwijderd, het probleem is opgelost.

Conclusie en ToDo

Alle code die in dit artikel wordt gebruikt, is in mijn repository. Ook zijn er voorbeelden van scripts en voorbeelden van het tellen van handtekeningen voor het registreren van webhooks.

Deze code is niets meer dan een voorbeeld van hoe u S3-webhooks kunt gebruiken in uw activiteiten. Zoals ik in het begin al zei, als je van plan bent zo'n server in productie te gebruiken, moet je de server op zijn minst herschrijven voor asynchroon werk: registreer inkomende webhooks in een wachtrij (RabbitMQ of NATS), en van daaruit parseer ze en verwerk ze met werknemersaanvragen. Anders kunt u, als er massaal webhooks binnenkomen, te maken krijgen met een gebrek aan serverbronnen om taken uit te voeren. Door de aanwezigheid van wachtrijen kunt u de server en werknemers verdelen en problemen oplossen met herhalende taken in geval van storingen. Het is ook raadzaam om de logboekregistratie te wijzigen in een meer gedetailleerde en meer gestandaardiseerde versie.

Good Luck!

Meer leesvoer over het onderwerp:

Bron: www.habr.com

Voeg een reactie