Et eksempel på en begivenhedsdrevet applikation baseret på webhooks i S3-objektlagring Mail.ru Cloud Solutions

Et eksempel på en begivenhedsdrevet applikation baseret på webhooks i S3-objektlagring Mail.ru Cloud Solutions
Rube Goldberg kaffemaskine

Hændelsesdrevet arkitektur øger omkostningseffektiviteten af ​​de anvendte ressourcer, fordi de kun bruges i det øjeblik, hvor de er nødvendige. Der er mange muligheder for at implementere dette og ikke oprette yderligere cloud-enheder som arbejdsapplikationer. Og i dag vil jeg ikke tale om FaaS, men om webhooks. Jeg viser et selvstudieeksempel på håndtering af hændelser ved hjælp af webhooks til objektlagring.

Et par ord om objektopbevaring og webhooks. Objektlagring giver dig mulighed for at gemme alle data i skyen i form af objekter, tilgængelige via S3 eller en anden API (afhængig af implementering) via HTTP/HTTPS. Webhooks er generelt tilpassede HTTP-tilbagekald. De udløses typisk af en hændelse, såsom kode, der bliver skubbet til et lager, eller en kommentar, der bliver postet på en blog. Når en hændelse opstår, sender oprindelsesstedet en HTTP-anmodning til den URL, der er angivet for webhook. Som et resultat kan du få hændelser på ét websted til at udløse handlinger på et andet (wiki). I det tilfælde, hvor kildestedet er et objektlager, fungerer hændelser som ændringer af dets indhold.

Eksempler på simple tilfælde, hvor sådan automatisering kan bruges:

  1. Oprettelse af kopier af alle objekter i et andet skylager. Kopier skal oprettes med det samme, når filer tilføjes eller ændres.
  2. Automatisk oprettelse af en række miniaturer af grafiske filer, tilføjelse af vandmærker til fotografier og andre billedændringer.
  3. Meddelelse om ankomsten af ​​nye dokumenter (for eksempel uploader en distribueret regnskabstjeneste rapporter til skyen, og økonomisk overvågning modtager meddelelser om nye rapporter, kontrollerer og analyserer dem).
  4. Lidt mere komplekse sager involverer for eksempel generering af en anmodning til Kubernetes, som opretter en pod med de nødvendige containere, sender opgaveparametre til den, og efter behandling kollapser containeren.

Som et eksempel vil vi lave en variant af opgave 1, når ændringer i Mail.ru Cloud Solutions (MCS) objektlagringsbøtten synkroniseres i AWS objektlagring ved hjælp af webhooks. I et rigtigt belastet tilfælde bør asynkront arbejde udføres ved at registrere webhooks i en kø, men til træningsopgaven vil vi udføre implementeringen uden dette.

Arbejdsplan

Interaktionsprotokollen er beskrevet detaljeret i Guide til S3 webhooks på MCS. Arbejdsplanen indeholder følgende elementer:

  • Forlagstjeneste, som er på S3-lagersiden og udgiver HTTP-anmodninger, når webnhook udløses.
  • Webhook modtagende server, som lytter til anmodninger fra HTTP-udgivelsestjenesten og udfører passende handlinger. Serveren kan skrives på ethvert sprog; i vores eksempel vil vi skrive serveren i Go.

Et særligt træk ved implementeringen af ​​webhooks i S3 API er registreringen af ​​webhook-modtagelsesserveren på udgivelsestjenesten. Især skal webhook-modtagende server bekræfte abonnementet på meddelelser fra publiceringstjenesten (i andre webhook-implementeringer er bekræftelse af abonnement normalt ikke påkrævet).

Følgelig skal webhook-modtagende server understøtte to hovedoperationer:

  • svare på forlagstjenestens anmodning om at bekræfte registreringen,
  • behandle indkommende begivenheder.

Installation af en webhook-modtageserver

For at køre webhook-modtagelsesserveren skal du bruge en Linux-server. I denne artikel bruger vi som eksempel en virtuel instans, som vi implementerer på MCS.

Lad os installere den nødvendige software og starte webhook-modtagelsesserveren.

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) ...

Klon mappen med webhook-modtagende 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.

Lad os starte serveren:

ubuntu@ubuntu-basic-1-2-10gb:~$ cd s3-webhook/
ubuntu@ubuntu-basic-1-2-10gb:~/s3-webhook$ sudo ./s3-webhook -port 80

Abonner på udgivelsestjeneste

Du kan registrere din webhook-modtageserver via API'en eller webgrænsefladen. For nemheds skyld registrerer vi via webgrænsefladen:

  1. Lad os gå til sektionen spande i kontrolrummet.
  2. Gå til den spand, som vi vil konfigurere webhooks til, og klik på gearet:

Et eksempel på en begivenhedsdrevet applikation baseret på webhooks i S3-objektlagring Mail.ru Cloud Solutions

Gå til fanen Webhooks, og klik på Tilføj:

Et eksempel på en begivenhedsdrevet applikation baseret på webhooks i S3-objektlagring Mail.ru Cloud Solutions
Udfyld felterne:

Et eksempel på en begivenhedsdrevet applikation baseret på webhooks i S3-objektlagring Mail.ru Cloud Solutions

ID — navnet på webhook.

Hændelse - hvilke hændelser der skal overføres. Vi har indstillet transmissionen af ​​alle hændelser, der opstår, når du arbejder med filer (tilføje og slette).

URL — webhook, der modtager serveradresse.

Filterpræfiks/suffiks er et filter, der giver dig mulighed for kun at generere webhooks for objekter, hvis navne matcher bestemte regler. For at webhook kun skal udløse filer med filtypenavnet .png, skal du f.eks Filtersuffiks du skal skrive "png".

I øjeblikket understøttes kun porte 80 og 443 til at få adgang til webhook-modtagelsesserveren.

Lad os klikke Tilføj krog og vi vil se følgende:

Et eksempel på en begivenhedsdrevet applikation baseret på webhooks i S3-objektlagring Mail.ru Cloud Solutions
Krog tilføjet.

Den webhook-modtagende server viser i sine logfiler forløbet af hook-registreringsprocessen:

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

Tilmeldingen er gennemført. I det næste afsnit vil vi se nærmere på algoritmen for driften af ​​webhook-modtagende server.

Beskrivelse af webhook-modtagende server

I vores eksempel er serveren skrevet i Go. Lad os se på de grundlæggende principper for dens drift.

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))
}

Overvej hovedfunktionerne:

  • Ping() - en rute, der reagerer via URL/ping, den enkleste implementering af en liveness-probe.
  • Webhook() - hovedrute, URL/webhook-handler:
    • bekræfter registrering på udgivelsestjenesten (gå til funktionen SubscriptionConfirmation),
    • behandler indgående webhooks (Gorecords funktion).
  • Funktionerne HmacSha256 og HmacSha256hex er implementeringer af HMAC-SHA256 og HMAC-SHA256 krypteringsalgoritmer med output som en streng af hexadecimale tal til beregning af signaturen.
  • main er hovedfunktionen, behandler kommandolinjeparametre og registrerer URL-handlere.

Kommandolinjeparametre accepteret af serveren:

  • -port er den port, som serveren vil lytte til.
  • -adresse - IP-adresse, som serveren vil lytte til.
  • -script er et eksternt program, der kaldes for hver indkommende hook.

Lad os se nærmere på nogle af funktionerne:

//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)
    }

}

Denne funktion bestemmer, om der er modtaget en anmodning om bekræftelse af registrering eller en webhook. Som følger af dokumentation, hvis registreringen bekræftes, modtages følgende Json-struktur i Post-anmodningen:

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»
}

Denne forespørgsel skal besvares:

content-type: application/json

{"signature":«ea3fce4bb15c6de4fec365d36bcebbc34ccddf54616d5ca12e1972f82b6d37af»}

Hvor signaturen er beregnet som:

signature = hmac_sha256(url, hmac_sha256(TopicArn, 
hmac_sha256(Timestamp, Token)))

Hvis der ankommer en webhook, ser strukturen af ​​Post-anmodningen sådan ud:

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"
            }
        }
    ]
}

Afhængigt af anmodningen skal du derfor forstå, hvordan du behandler dataene. Jeg valgte posten som en indikator "Type":"SubscriptionConfirmation", da det er til stede i anmodningen om abonnementsbekræftelse og ikke er til stede i webhook. Baseret på tilstedeværelsen/fraværet af denne post i POST-anmodningen, går yderligere udførelse af programmet enten til funktionen SubscriptionConfirmation, eller ind i funktionen GotRecords.

Vi vil ikke overveje abonnementsbekræftelsesfunktionen i detaljer; den implementeres i overensstemmelse med principperne angivet i dokumentation. Du kan se kildekoden til denne funktion på projekt git repositories.

GotRecords-funktionen analyserer en indgående anmodning og kalder for hvert Record-objekt et eksternt script (hvis navn blev videregivet i parameteren -script) med parametrene:

  • spand navn
  • objekt nøgle
  • handling:
    • kopi - hvis i den oprindelige anmodning EventName = ObjectCreated | PutObject | PutObjectCopy
    • slet - hvis i den oprindelige anmodning EventName = ObjectRemoved | Slet objekt

Hvis der således ankommer en krog med en Post-anmodning, som beskrevet ovenfor, og parameteren -script=script.sh, så kaldes scriptet som følger:

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

Det skal forstås, at denne webhook-modtageserver ikke er en komplet produktionsløsning, men et forenklet eksempel på en mulig implementering.

Eksempel på arbejde

Lad os synkronisere filerne fra hovedbøtten i MCS til backup-bøtten i AWS. Den primære bucket hedder myfiles-ash, den backup kaldes myfiles-backup (bucket-konfiguration i AWS er ​​uden for denne artikels omfang). Følgelig, når en fil placeres i hovedbøtten, skal dens kopi vises i backup-en, og når den slettes fra den primære, skal den slettes i backup-en.

Vi vil arbejde med buckets ved hjælp af awscli-værktøjet, som er kompatibelt med både MCS cloud storage og AWS cloud storage.

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) ...

Lad os konfigurere adgang til S3 MCS API:

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]:

Lad os konfigurere adgang til AWS S3 API:

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]:

Lad os tjekke adgangene:

Til AWS:

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

For MCS, når du kører kommandoen, skal du tilføje —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-ash

Tilgået.

Lad os nu skrive et script til behandling af den indgående hook, lad os kalde det 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=""
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

Lad os starte serveren:

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

Lad os se, hvordan det virker. igennem MCS webgrænseflade tilføj test.txt-filen til myfiles-ash-bøtten. Konsollogfilerne viser, at der blev foretaget en anmodning til webhook-serveren:

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

Lad os tjekke indholdet af myfiles-backup-bøtten i AWS:

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 vil vi via webgrænsefladen slette filen fra myfiles-ash-bøtten.

Serverlogfiler:

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

Spandens indhold:

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

Filen er slettet, problemet er løst.

Konklusion og ToDo

Al kode brugt i denne artikel er i mit depot. Der er også eksempler på scripts og eksempler på optælling af signaturer til registrering af webhooks.

Denne kode er intet andet end et eksempel på, hvordan du kan bruge S3 webhooks i dine aktiviteter. Som jeg sagde i begyndelsen, hvis du planlægger at bruge sådan en server i produktionen, skal du i det mindste omskrive serveren til asynkront arbejde: registrere indgående webhooks i en kø (RabbitMQ eller NATS), og derfra parse dem og behandle dem med arbejderansøgninger. Ellers, når webhooks kommer massivt, kan du støde på mangel på serverressourcer til at fuldføre opgaver. Tilstedeværelsen af ​​køer giver dig mulighed for at distribuere serveren og arbejderne samt løse problemer med gentagne opgaver i tilfælde af fejl. Det er også tilrådeligt at ændre logningen til en mere detaljeret og mere standardiseret.

Held og lykke!

Mere læsning om emnet:

Kilde: www.habr.com

Tilføj en kommentar