Eksempel pÄ en event-driven applikation baseret pÄ webhooks i objekt-S3-lagring fra Mail.ru Cloud Solutions

Eksempel pÄ en event-driven applikation baseret pÄ webhooks i objekt-S3-lagring fra Mail.ru Cloud Solutions
Rube Goldberg kaffemaskine

Event-drevet arkitektur forbedrer omkostningseffektiviteten af de anvendte ressourcer, da de kun aktiveres, nÄr de er nÞdvendige. Der er mange mÄder at implementere dette pÄ uden at oprette ekstra cloud-forekomster som worker-applikationer. I dag vil jeg ikke tale om FaaS, men om webhooks. Jeg vil vise et eksempel pÄ, hvordan man hÄndterer begivenheder ved hjÊlp af webhooks til objektlagring.

Et par ord om objektlagring og webhooks. Objektlagre giver mulighed for at gemme data i skyen som objekter, der er tilgÊngelige via S3 eller en anden API (afhÊnger af implementeringen) via HTTP/HTTPS. Webhooks er generelt brugerdefinerede tilbagekaldelser via HTTP. De aktiveres typisk af en begivenhed, sÄsom at sende kode til et repository eller en kommentar, der offentliggÞres i en blog. NÄr en begivenhed indtrÊffer, sender den oprindelige hjemmeside en HTTP-anmodning til den URL, der er angivet for webhook'en. Dette kan fÞre til, at begivenheder pÄ ét website udlÞser handlinger pÄ et andet. I tilfÊlde, hvor den oprindelige hjemmeside er et objektlager, fungerer Êndringerne af indholdet som begivenheder.wiki). I tilfÊlde, hvor den oprindelige hjemmeside fungerer som et objektlager, udgÞr Êndringer i dens indhold begivenheder.

Eksempler pÄ enkle cases, hvor sÄdan automatisering kan anvendes:

  1. Oprettelse af kopier af alle objekter i et andet cloud-lager. Kopier skal oprettes 'on-the-fly', nÄr filer tilfÞjes eller Êndres.
  2. Automatisk oprettelse af serier af miniaturebilleder, tilfĂžjelse af vandmĂŠrker til fotos, andre billedmodifikationer.
  3. Underretning om ankomsten af nye dokumenter (for eksempel, hvis en distribueret bogholderitjeneste uploader rapporter til skyen, mens finansovervÄgning fÄr meddelelser om nye rapporter og derefter tjekker og analyserer dem).
  4. Lidt mere komplekse cases indebĂŠrer for eksempel dannelse af en anmodning til Kubernetes, som opretter en pod med de nĂždvendige containere, overfĂžrer opgaveparametre til den, og efter behandling lukker containeren.

Som eksempel vil vi lave en version af opgave 1, hvor ĂŠndringer i objektlageret Mail.ru Cloud Solutions (MCS) synkroniseres til AWS objektlager via webhooks. I et reelt scenario med hĂžj belastning bĂžr der overvejes asynkron drift ved at registrere webhooks i en kĂž, men til denne lĂŠringsopgave vil vi implementere det uden dette.

Arbejdsschema

Protokollet for interaktion er detaljeret beskrevet i vejledningen til S3-webhooks pÄ MCS. I arbejdsschemaet er der fÞlgende elementer:

  • Publikationsservice, som findes pĂ„ S3-lageret og publicerer HTTP-anmodninger ved aktivering af webhook.
  • Webhook-modtagerserver, som lytter til anmodninger fra publikationsservicen via HTTP og udfĂžrer de relevante handlinger. Serveren kan skrives i hvilket som helst sprog; i vores eksempel vil vi skrive serveren i Go.

En sĂŠrprĂŠget ved implementeringen af webhooks i S3 API’en er registreringen af webhook-modtagerserveren pĂ„ publikationsservicen. Specifikt skal webhook-modtagerserveren bekrĂŠfte abonnementet pĂ„ meddelelser fra publikationsservicen (i andre implementeringer krĂŠves det normalt ikke at bekrĂŠfte abonnementsstatus).

Dermed skal webhook-modtagerserveren understĂžtte to grundlĂŠggende operationer:

  • at svare pĂ„ publikationsservicens anmodning om abonnementsbekrĂŠftelse,
  • og at behandle indkommende hĂŠndelser.

Installation af webhook-modtagerserveren

For at starte webhook-modtagerserveren krÊves der en Linux-server. I denne artikel vil vi bruge et virtuelt instans som eksempel, som vi opretter pÄ MCS.

Vi installerer det nĂždvendige software og starter webhook-modtagerserveren.

ubuntu@ubuntu-basic-1-2-10gb:~$ sudo apt-get install git
LĂŠsning af pakkeoversigter... UdfĂžrt
Bygger afhĂŠngighedstrĂŠ
LĂŠsning af tilstandsoplysninger... UdfĂžrt
FĂžlgende pakker blev automatisk installeret og er ikke lĂŠngere nĂždvendige:
 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
Brug 'sudo apt autoremove' for at fjerne dem.
Forslag til pakker:
 git-daemon-run | git-daemon-sysvinit git-doc git-el git-email git-gui 
gitk gitweb git-cvs git-mediawiki git-svn
FĂžlgende NYE pakker vil blive installeret:
 git
0 opgraderet, 1 nyinstalleret, 0 til at fjerne og 46 ikke opgraderet.
Skal hente 3915 kB arkiver.
Efter denne operation vil 32,3 MB ekstra diskplads blive brugt.
Hent:1 http://MS1.clouds.archive.ubuntu.com/ubuntu bionic-updates/main 
amd64 git amd64 1:2.17.1-1ubuntu0.7 [3915 kB]
Hentet 3915 kB pÄ 1s (5639 kB/s)
VĂŠlger tidligere ikke-valgt pakke git.
(LĂŠser database ... 53932 filer og mapper er i Ăžjeblikket installeret.)
Forbereder til at pakke ud .../git_12.17.1-1ubuntu0.7_amd64.deb ...
Udpakker git (1:2.17.1-1ubuntu0.7) ...
SĂŠtter git op (1:2.17.1-1ubuntu0.7) ...

Vi kloner mappen med webhook-modtagerserveren:

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

Tilmelding til publiceringsservice

Du kan registrere din webhook-modtagerserver via API eller webgrĂŠnseflade. For enkelhedens skyld registrerer vi via webgrĂŠnsefladen:

  1. GĂ„ til sektionen for buckets i kontrolpanelet.
  2. GÄ ind i den bucket, som vi vil konfigurere webhooks for, og klik pÄ tandhjulet:

Eksempel pÄ en event-driven applikation baseret pÄ webhooks i objekt-S3-lagring fra Mail.ru Cloud Solutions

GÄ til fanen Webhooks og klik pÄ TilfÞj:

Eksempel pÄ en event-driven applikation baseret pÄ webhooks i objekt-S3-lagring fra Mail.ru Cloud Solutions
Udfyld felterne:

Eksempel pÄ en event-driven applikation baseret pÄ webhooks i objekt-S3-lagring fra Mail.ru Cloud Solutions

ID — navnet pĂ„ webhooken.

HĂŠndelse — hvilke hĂŠndelser der skal videregives. Vi har angivet videregivelse af alle hĂŠndelser, der opstĂ„r ved arbejde med filer (tilfĂžjelse og sletning).

URL — adressen pĂ„ webhook-modtagerserveren.

Filter prefix/suffix — et filter, der gþr det muligt at generere webhooks kun for objekter, hvis navne opfylder bestemte regler. For eksempel, for at webhooken kun skal udlþses for filer med filtypen .png, skal du Filter suffix indsétte „png“.

I Ăžjeblikket understĂžttes kun porte 80 og 443 til forbindelse til webhook-modtagerserveren.

Lad os trykke pÄ TilfÞj hook og vi vil se fÞlgende:

Eksempel pÄ en event-driven applikation baseret pÄ webhooks i objekt-S3-lagring fra Mail.ru Cloud Solutions
Hook tilfĂžjet.

Webhook-modtagerserveren viser i loggen, at registreringsprocessen for hooken er gennemfĂžrt:

ubuntu@ubuntu-basic-1-2-10gb:~/s3-webhook$ sudo ./s3-webhook -port 80
2020/06/15 12:01:14 [POST] indkommende HTTP-anmodning fra 
95.163.216.92:42530
2020/06/15 12:01:14 Modtog tidsstempel: 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 Generer respons-signatur: 
3754ce36636f80dfd606c5254d64ecb2fd8d555c27962b70b4f759f32c76b66d

Registreringen er afsluttet. I det nÊste afsnit vil vi se nÊrmere pÄ, hvordan webhook-modtagerserveren fungerer.

Beskrivelse af webhook-modtagerserveren

I vores eksempel er serveren skrevet i Go. Lad os gennemgÄ de grundlÊggende principper for dens funktion.

package main

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

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

// Send bekrĂŠftelse af abonnement
func SubscriptionConfirmation(w http.ResponseWriter, req *http.Request, body []byte) {
}

// Send bekrĂŠftelse af abonnement
func GotRecords(w http.ResponseWriter, req *http.Request, body []byte) {
}

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

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

func main() {

    // hent kommandolinjeargumenter
    bindPort := flag.Int("port", 80, "nummer mellem 1-65535")
    bindAddr := flag.String("address", "", "IP-adresse i punktformat")
    flag.StringVar(&actionScript, "script", "", "ekstern script til udfĂžrelse")
    flag.Parse()

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

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

Lad os se pÄ de grundlÊggende funktioner:

  • Ping() — en rute, der svarer pĂ„ URL/ping, en simpel implementering af liveness probe.
  • Webhook() — hovedrute, behandler URL/webhook:
    • bekrĂŠfter registreringen pĂ„ publiceringstjenesten (overgĂ„r til funktionen SubscriptionConfirmation),
    • behandler indkommende webhooks (funktionen Gotrecords).
  • Funktionerne HmacSha256 og HmacSha256hex — implementeringer af HMAC-SHA256 og HMAC-SHA256-algoritmerne med output i form af en strĂžm af hexadecimale tal til beregning af signatur.
  • main — hovedfunktionen, der behandler kommandolinjeargumenter og registrerer URL-behandlere.

Kommandolinjeargumenter, der modtages af serveren:

  • -port — porten, som serveren vil lytte pĂ„.
  • -address — IP-adressen, som serveren vil lytte pĂ„.
  • -script — et eksternt program, som kaldes for hver indkommende webhook.

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, hvad der er kommet — en anmodning om bekréftelse af registreringen eller en webhook. Som angivet i dokumentationen, i tilfélde af en bekréftelse af registrering 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":"Du har valgt at abonnere pÄ emnet $topic. For at bekrÊfte abonnementsanmodningen skal du svare med den beregnede signatur",
    "TopicArn":"mcs2883541269|bucketA|s3:ObjectCreated:Put",
    "SignatureVersion":1,
    "Token":"RPE5UuG94rGgBH6kHXN9FUPugFxj1hs2aUQc99btJp3E49tA"
}

Dette svar skal gives til anmodningen:

content-type: application/json

{"signature":"ea3fce4bb15c6de4fec365d36bcebbc34ccddf54616d5ca12e1972f82b6d37af"}

Hvor signaturen beregnes som:

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

Hvis der derimod kommer en webhook, ser POST-anmodningens struktur sÄdan ud:

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

AfhÊngigt af anmodningen skal man forstÄ, hvordan dataene skal behandles. Jeg har valgt posten som indikator "Type":"SubscriptionConfirmation", da det er til stede i anmodningen om at bekrÊfte abonnementet og ikke findes i webhooken. Baseret pÄ tilstedevÊrelsen/fravÊret af denne post i POST-anmodningen, gÄr programmet videre til enten funktionen SubscriptionConfirmation, eller til funktionen GotRecords.

Vi vil ikke gÄ ind i detaljerne omkring funktionen SubscriptionConfirmation, da den er implementeret efter de principper, der er beskrevet i dokumentationen. Kildekoden til denne funktion kan findes i git-repositoriet for projektet.

Funktionen GotRecords analyserer den indkommende anmodning og for hvert Record-objekt kalder den et eksternt script (navnet blev givet i parameteren -script) med parametrene:

  • navnet pĂ„ bucket'en
  • nĂžgle til objektet
  • handling:
    • copy — hvis EventName = ObjectCreated | PutObject | PutObjectCopy i den oprindelige anmodning
    • delete — hvis EventName = ObjectRemoved | DeleteObject i den oprindelige anmodning

SÄledes, hvis der kom en webhook med en Post-anmodning, som beskrevet over, og parameteren -script=script.sh, sÄ vil scriptet blive kaldt som fÞlger:

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

Det skal forstÄs, at denne webhook-modtager-server ikke er en fÊrdig lÞsning til produktion, men et forenklet eksempel pÄ en mulig implementering.

Et eksempel pÄ arbejdet

Lad os synkronisere filerne fra hovedbucket'en i MCS til backupbucket'en i AWS. Hovedbucket'en kaldes myfiles-ash, og backupbucket'en er myfiles-backup (konfigurationen af bucket'en i AWS overskrider denne artikels rammer). NÄr en fil lÊgges i hovedbucket'en, skal en kopi vises i backupbucket'en, og nÄr den slettes fra hovedbucket'en, skal den slettes i backupbucket'en.

Vi vil arbejde med bucket'erne ved hjÊlp af vÊrktÞjet awscli, som er kompatibelt med bÄde MCS- og AWS-cloudlager.

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 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
Default region name [None]:
Default output format [None]:

Lad os konfigurere adgang til 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
Default region name [None]:
Default output format [None]:

Lad os teste adgangen:

Til AWS:

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

For MCS skal man tilfþje —endpoint-url til kommandoen:

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

Adgang givet.

Lad os nu skrive et script til behandling af den indkommende webhook, som vi kalder 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
    ;;
esac

Starter 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 fungerer. Gennem MCS webgrĂŠnseflade tilfĂžjer vi filen test.txt til bucket myfiles-ash. I loggen i konsollen ses det, at der blev sendt en anmodning til webhook-serveren:

2020\/07\/06 09:43:08 [POST] indkommende HTTP-anmodning fra 
95.163.216.92:56612
download: s3:\/\/myfiles-ash\/test.txt til ..\/..\/..\/tmp\/myfiles-ash\/test.txt
upload: ..\/..\/..\/tmp\/myfiles-ash\/test.txt til 
s3:\/\/myfiles-backup\/test.txt

Lad os kontrollere indholdet af bucket myfiles-backup 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 fjerner vi filen fra bucket myfiles-ash via webgrĂŠnsefladen.

Serverlogfiler:

2020\/07\/06 09:44:46 [POST] indkommende HTTP-anmodning fra 
95.163.216.92:58224
delete: s3:\/\/myfiles-backup\/test.txt

Indholdet af bucketen:

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

Filen er blevet slettet, opgaven er lĂžst.

Konklusion og ToDo

Al koden, der anvendes i denne artikel, findes i mit repository. Her ligger ogsÄ eksempler pÄ scripts og eksempler pÄ beregning af signaturer til registrering af webhooks.

Denne kode er ikke mere end et eksempel pÄ, hvordan man kan bruge S3-webhooks i sin virksomhed. Som jeg nÊvnte indledningsvis, hvis man planlÊgger at anvende en sÄdan server i produktion, bÞr man i det mindste omskrive serveren til asynkron drift: registrere indkommende webhooks i en kÞ (RabbitMQ eller NATS), og derfra behandle dem med worker-applikationer. Ellers kan man opleve ressourceknaphed pÄ serveren ved et massivt antal indkommende webhooks. At have kÞer muliggÞr distribution af server og workers, samt at hÄndtere genbehandling af opgaver i tilfÊlde af fejl. Det er ogsÄ Þnskeligt at Êndre logningen til mere detaljeret og standardiseret.

Held og lykke!

Yderligere lĂŠsning om emnet:

Kilde: habr.com

KĂžb pĂ„lidelig webhosting med DDoS-beskyttelse, VPS VDS-servere đŸ”„ KĂžb pĂ„lidelig webhosting med DDoS-beskyttelse, VPS VDS-servere | ProHoster