Et eksempel på en hendelsesdrevet applikasjon basert på webhooks i S3-objektlagringen til Mail.ru Cloud Solutions

Et eksempel på en hendelsesdrevet applikasjon basert på webhooks i S3-objektlagringen til Mail.ru Cloud Solutions
Rube Goldberg kaffemaskin

Hendelsesdrevet arkitektur øker kostnadseffektiviteten til ressursene som brukes fordi de bare brukes i det øyeblikket de er nødvendige. Det er mange alternativer for hvordan du implementerer dette og ikke oppretter flere skyenheter som arbeiderapplikasjoner. Og i dag skal jeg ikke snakke om FaaS, men om webhooks. Jeg skal vise et opplæringseksempel på håndtering av hendelser ved hjelp av webhooks for objektlagring.

Noen få ord om objektlagring og webhooks. Objektlagring lar deg lagre alle data i skyen i form av objekter, tilgjengelig via S3 eller en annen API (avhengig av implementering) via HTTP/HTTPS. Webhooks er vanligvis tilpassede HTTP-tilbakekallinger. De utløses vanligvis av en hendelse, for eksempel kode som blir presset til et depot eller en kommentar som legges ut på en blogg. Når en hendelse inntreffer, sender opprinnelsesnettstedet en HTTP-forespørsel til URL-en som er spesifisert for webhook. Som et resultat kan du få hendelser på ett nettsted til å utløse handlinger på et annet (wiki). I tilfellet der kildenettstedet er et objektlager, fungerer hendelser som endringer i innholdet.

Eksempler på enkle tilfeller der slik automatisering kan brukes:

  1. Lage kopier av alle objekter i en annen skylagring. Kopier må opprettes umiddelbart når filer legges til eller endres.
  2. Automatisk oppretting av en serie miniatyrbilder av grafikkfiler, tilføying av vannmerker til fotografier og andre bildeendringer.
  3. Varsling om ankomst av nye dokumenter (for eksempel laster en distribuert regnskapstjeneste opp rapporter til skyen, og økonomisk overvåking mottar varsler om nye rapporter, kontrollerer og analyserer dem).
  4. Litt mer komplekse saker innebærer for eksempel å generere en forespørsel til Kubernetes, som lager en pod med de nødvendige beholderne, sender oppgaveparametere til den, og etter behandling kollapser beholderen.

Som et eksempel vil vi lage en variant av oppgave 1, når endringer i Mail.ru Cloud Solutions (MCS) objektlagringsbøtte synkroniseres i AWS objektlagring ved hjelp av webhooks. I et reelt belastet tilfelle bør asynkront arbeid gis ved å registrere webhooks i en kø, men for opplæringsoppgaven vil vi gjøre implementeringen uten dette.

Arbeidssystem

Interaksjonsprotokollen er beskrevet i detalj i Veiledning til S3 webhooks på MCS. Arbeidsordningen inneholder følgende elementer:

  • Publiseringstjeneste, som er på S3-lagringssiden og publiserer HTTP-forespørsler når webnhook utløses.
  • Webhook mottaksserver, som lytter til forespørsler fra HTTP-publiseringstjenesten og utfører passende handlinger. Serveren kan skrives på et hvilket som helst språk; i vårt eksempel vil vi skrive serveren i Go.

En spesiell funksjon ved implementeringen av webhooks i S3 API er registreringen av webhook-mottaksserveren på publiseringstjenesten. Spesielt må webhook-mottaksserveren bekrefte abonnementet på meldinger fra publiseringstjenesten (i andre webhook-implementeringer er bekreftelse av abonnement vanligvis ikke nødvendig).

Følgelig må webhook-mottaksserveren støtte to hovedoperasjoner:

  • svare på publiseringstjenestens forespørsel om å bekrefte registreringen,
  • behandle innkommende hendelser.

Installere en webhook-mottaksserver

For å kjøre webhook-mottaksserveren trenger du en Linux-server. I denne artikkelen bruker vi som eksempel en virtuell forekomst som vi distribuerer på MCS.

La oss installere nødvendig programvare og starte webhook-mottaksserveren.

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

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.

La oss 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å publiseringstjeneste

Du kan registrere webhook-mottaksserveren din via API eller webgrensesnitt. For enkelhets skyld vil vi registrere oss via webgrensesnittet:

  1. La oss gå til bøtter-delen i kontrollrommet.
  2. Gå til bøtta som vi skal konfigurere webhooks for og klikk på tannhjulet:

Et eksempel på en hendelsesdrevet applikasjon basert på webhooks i S3-objektlagringen til Mail.ru Cloud Solutions

Gå til Webhooks-fanen og klikk på Legg til:

Et eksempel på en hendelsesdrevet applikasjon basert på webhooks i S3-objektlagringen til Mail.ru Cloud Solutions
Fyll ut feltene:

Et eksempel på en hendelsesdrevet applikasjon basert på webhooks i S3-objektlagringen til Mail.ru Cloud Solutions

ID — navnet på webhook.

Hendelse - hvilke hendelser som skal overføres. Vi har satt overføring av alle hendelser som oppstår når du arbeider med filer (legger til og sletter).

URL — webhook som mottar serveradresse.

Filterprefiks/suffiks er et filter som lar deg generere webhooks kun for objekter hvis navn samsvarer med visse regler. For eksempel, for at webhook skal utløse bare filer med filtypen .png, i Filtersuffiks du må skrive "png".

For øyeblikket støttes bare portene 80 og 443 for tilgang til webhook-mottaksserveren.

La oss klikke Legg til krok og vi vil se følgende:

Et eksempel på en hendelsesdrevet applikasjon basert på webhooks i S3-objektlagringen til Mail.ru Cloud Solutions
Krok lagt til.

Webhook-mottaksserveren viser i sine logger fremdriften til hook-registreringsprosessen:

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

Registreringen er fullført. I neste avsnitt vil vi se nærmere på algoritmen for drift av webhook-mottaksserveren.

Beskrivelse av webhook-mottaksserveren

I vårt eksempel er serveren skrevet i Go. La oss se på de grunnleggende prinsippene for driften.

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

Vurder hovedfunksjonene:

  • Ping() - en rute som svarer via URL/ping, den enkleste implementeringen av en liveness-sonde.
  • Webhook() - hovedrute, URL/webhook-behandler:
    • bekrefter registrering på publiseringstjenesten (gå til funksjonen SubscriptionConfirmation),
    • behandler innkommende webhooks (Gorecords-funksjon).
  • Funksjonene HmacSha256 og HmacSha256hex er implementeringer av HMAC-SHA256 og HMAC-SHA256 krypteringsalgoritmer med utdata som en streng med heksadesimale tall for beregning av signaturen.
  • main er hovedfunksjonen, behandler kommandolinjeparametere og registrerer URL-behandlere.

Kommandolinjeparametere akseptert av serveren:

  • -port er porten som serveren vil lytte til.
  • -adresse - IP-adresse som serveren vil lytte til.
  • -script er et eksternt program som kalles for hver innkommende krok.

La oss se nærmere på noen av funksjonene:

//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 funksjonen bestemmer om en forespørsel om å bekrefte registrering eller en webhook har kommet. Som følger av dokumentasjon, hvis registreringen er bekreftet, mottas følgende Json-struktur i Post-forespørselen:

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

Dette spørsmålet må besvares:

content-type: application/json

{"signature":«ea3fce4bb15c6de4fec365d36bcebbc34ccddf54616d5ca12e1972f82b6d37af»}

Hvor signaturen er beregnet som:

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

Hvis en webhook kommer, ser strukturen til Post-forespørselen slik ut:

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

Følgelig, avhengig av forespørselen, må du forstå hvordan du behandler dataene. Jeg valgte oppføringen som en indikator "Type":"SubscriptionConfirmation", siden den er til stede i forespørselen om abonnementsbekreftelse og ikke er til stede i webhook. Basert på tilstedeværelsen/fraværet av denne oppføringen i POST-forespørselen, går videre kjøring av programmet enten til funksjonen SubscriptionConfirmation, eller inn i funksjonen GotRecords.

Vi vil ikke vurdere funksjonen for abonnementsbekreftelse i detalj; den implementeres i henhold til prinsippene angitt i dokumentasjon. Du kan se kildekoden for denne funksjonen på prosjekt git repositories.

GotRecords-funksjonen analyserer en innkommende forespørsel og kaller for hvert Record-objekt et eksternt skript (hvis navn ble sendt i -script-parameteren) med parameterne:

  • bøttenavn
  • objektnøkkel
  • handling:
    • kopi - hvis i den opprinnelige forespørselen EventName = ObjectCreated | PutObject | PutObjectCopy
    • slett - hvis i den opprinnelige forespørselen EventName = ObjectRemoved | SlettObjekt

Dermed, hvis en krok kommer med en Post-forespørsel, som beskrevet ovenfor, og parameteren -script=script.sh, vil skriptet kalles som følger:

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

Det skal forstås at denne webhook-mottaksserveren ikke er en komplett produksjonsløsning, men et forenklet eksempel på en mulig implementering.

Eksempel på arbeid

La oss synkronisere filene fra hovedbøtten i MCS til backupbøtten i AWS. Hovedbøtten kalles myfiles-ash, den sikkerhetskopien kalles myfiles-backup (bøttekonfigurasjonen i AWS er ​​utenfor rammen av denne artikkelen). Følgelig, når en fil plasseres i hovedbøtten, bør kopien vises i den sikkerhetskopiente, og når den slettes fra den viktigste, bør den slettes i sikkerhetskopifilen.

Vi vil jobbe med bøtter ved å bruke awscli-verktøyet, som er kompatibelt med både MCS skylagring og AWS skylagring.

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

La oss konfigurere tilgang 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]:

La oss konfigurere tilgang 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]:

La oss sjekke tilgangene:

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 kjører kommandoen, må du legge til —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

Tilgang.

La oss nå skrive et skript for å behandle den innkommende kroken, la oss kalle 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

La oss starte serveren:

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

La oss se hvordan det fungerer. Gjennom MCS webgrensesnitt legg til test.txt-filen i myfiles-ash-bøtten. Konsollloggene viser at en forespørsel ble sendt 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

La oss sjekke innholdet i 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

Nå, gjennom nettgrensesnittet, vil vi slette filen fra myfiles-ash-bøtten.

Serverlogger:

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

Innhold i bøtte:

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.

Konklusjon og ToDo

All kode som brukes i denne artikkelen er i lageret mitt. Det finnes også eksempler på skript og eksempler på telling av signaturer for registrering av webhooks.

Denne koden er ikke mer enn et eksempel på hvordan du kan bruke S3 webhooks i aktivitetene dine. Som jeg sa i begynnelsen, hvis du planlegger å bruke en slik server i produksjon, må du i det minste skrive om serveren for asynkront arbeid: registrere innkommende webhooks i en kø (RabbitMQ eller NATS), og derfra analysere dem og behandle dem med arbeidersøknader. Ellers, når webhooks kommer massivt, kan du støte på mangel på serverressurser for å fullføre oppgaver. Tilstedeværelsen av køer lar deg distribuere serveren og arbeiderne, samt løse problemer med gjentatte oppgaver i tilfelle feil. Det er også lurt å endre loggingen til en mer detaljert og mer standardisert.

Lykke til!

Mer lesing om temaet:

Kilde: www.habr.com

Legg til en kommentar