Ett exempel på en händelsedriven applikation baserad på webhooks i S3-objektlagring Mail.ru Cloud Solutions

Ett exempel på en händelsedriven applikation baserad på webhooks i S3-objektlagring Mail.ru Cloud Solutions
Rube Goldberg kaffemaskin

Händelsedriven arkitektur ökar kostnadseffektiviteten för de resurser som används eftersom de bara används i det ögonblick då de behövs. Det finns många alternativ för hur man implementerar detta och inte skapar ytterligare molnenheter som arbetarapplikationer. Och idag ska jag inte prata om FaaS, utan om webhooks. Jag ska visa ett självstudieexempel på hantering av händelser med hjälp av webhooks för objektlagring.

Några ord om objektlagring och webhooks. Objektlagring låter dig lagra all data i molnet i form av objekt, tillgängliga via S3 eller annat API (beroende på implementering) via HTTP/HTTPS. Webhooks är i allmänhet anpassade HTTP-återuppringningar. De utlöses vanligtvis av en händelse, till exempel att kod skickas till ett arkiv eller en kommentar som läggs upp på en blogg. När en händelse inträffar skickar ursprungsplatsen en HTTP-begäran till den URL som anges för webhook. Som ett resultat kan du få händelser på en webbplats att utlösa åtgärder på en annan (wiki). I det fall där källplatsen är en objektlagring fungerar händelser som ändringar av dess innehåll.

Exempel på enkla fall då sådan automatisering kan användas:

  1. Skapa kopior av alla objekt i en annan molnlagring. Kopior måste skapas i farten när filer läggs till eller ändras.
  2. Automatiskt skapande av en serie miniatyrer av grafiska filer, lägga till vattenstämplar på fotografier och andra bildändringar.
  3. Meddelande om ankomsten av nya dokument (till exempel laddar en distribuerad redovisningstjänst upp rapporter till molnet och finansövervakningen tar emot aviseringar om nya rapporter, kontrollerar och analyserar dem).
  4. Något mer komplexa fall innebär till exempel att generera en begäran till Kubernetes, som skapar en pod med nödvändiga behållare, skickar uppgiftsparametrar till den och efter bearbetning kollapsar behållaren.

Som ett exempel kommer vi att göra en variant av uppgift 1, när ändringar i Mail.ru Cloud Solutions (MCS) objektlagringshink synkroniseras i AWS-objektlagring med hjälp av webhooks. I ett riktigt laddat fall bör asynkront arbete tillhandahållas genom att registrera webhooks i en kö, men för utbildningsuppgiften kommer vi att göra implementeringen utan detta.

Arbetsordning

Interaktionsprotokollet beskrivs i detalj i Guide till S3 webhooks på MCS. Arbetsschemat innehåller följande element:

  • Publiceringstjänst, som finns på S3-lagringssidan och publicerar HTTP-förfrågningar när webnhook utlöses.
  • Webhook mottagande server, som lyssnar på förfrågningar från HTTP-publiceringstjänsten och utför lämpliga åtgärder. Servern kan skrivas på vilket språk som helst, i vårt exempel kommer vi att skriva servern i Go.

En speciell egenskap för implementeringen av webhooks i S3 API är registreringen av webhook-mottagningsservern på publiceringstjänsten. Speciellt måste den webhook-mottagande servern bekräfta prenumerationen på meddelanden från publiceringstjänsten (i andra webhook-implementationer krävs vanligtvis inte bekräftelse av prenumeration).

Följaktligen måste webhook-mottagande servern stödja två huvudoperationer:

  • svara på förlagstjänstens begäran om att bekräfta registreringen,
  • bearbeta inkommande händelser.

Installation av en webhook-mottagningsserver

För att köra webhook-mottagningsservern behöver du en Linux-server. I den här artikeln använder vi som exempel en virtuell instans som vi distribuerar på MCS.

Låt oss installera den nödvändiga programvaran och starta webhook-mottagningsservern.

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

Klona mappen med webhook-mottagningsservern:

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.

Låt oss starta servern:

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

Prenumerera på publiceringstjänst

Du kan registrera din webhook-mottagningsserver via API:et eller webbgränssnittet. För enkelhetens skull kommer vi att registrera oss via webbgränssnittet:

  1. Låt oss gå till sektionen hinkar i kontrollrummet.
  2. Gå till hinken som vi kommer att konfigurera webhooks för och klicka på kugghjulet:

Ett exempel på en händelsedriven applikation baserad på webhooks i S3-objektlagring Mail.ru Cloud Solutions

Gå till fliken Webhooks och klicka på Lägg till:

Ett exempel på en händelsedriven applikation baserad på webhooks i S3-objektlagring Mail.ru Cloud Solutions
Fyll i fälten:

Ett exempel på en händelsedriven applikation baserad på webhooks i S3-objektlagring Mail.ru Cloud Solutions

ID — namnet på webhook.

Händelse - vilka händelser som ska överföras. Vi har ställt in överföringen av alla händelser som inträffar när man arbetar med filer (lägger till och raderar).

URL — webbhook mottagande serveradress.

Filterprefix/suffix är ett filter som låter dig generera webhooks endast för objekt vars namn matchar vissa regler. Till exempel, för att webhook endast ska trigga filer med filtillägget .png, in Filtersuffix du måste skriva "png".

För närvarande stöds endast portarna 80 och 443 för åtkomst till webhook-mottagningsservern.

Låt oss klicka Lägg till krok och vi kommer att se följande:

Ett exempel på en händelsedriven applikation baserad på webhooks i S3-objektlagring Mail.ru Cloud Solutions
Krok tillagd.

Webhook-mottagningsservern visar i sina loggar förloppet för 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

Registreringen är klar. I nästa avsnitt kommer vi att titta närmare på algoritmen för driften av webhook-mottagningsservern.

Beskrivning av webhook-mottagande server

I vårt exempel är servern skriven i Go. Låt oss titta på de grundläggande principerna för dess funktion.

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

Tänk på huvudfunktionerna:

  • Ping() - en rutt som svarar via URL/ping, den enklaste implementeringen av en liveness-prob.
  • Webhook() - huvudväg, URL/webhook-hanterare:
    • bekräftar registreringen på publiceringstjänsten (gå till funktionen Prenumerationsbekräftelse),
    • bearbetar inkommande webhooks (Gorecords-funktion).
  • Funktionerna HmacSha256 och HmacSha256hex är implementeringar av krypteringsalgoritmerna HMAC-SHA256 och HMAC-SHA256 med utdata som en sträng av hexadecimala tal för beräkning av signaturen.
  • main är huvudfunktionen, bearbetar kommandoradsparametrar och registrerar URL-hanterare.

Kommandoradsparametrar som accepteras av servern:

  • -port är den port som servern kommer att lyssna på.
  • -adress - IP-adress som servern kommer att lyssna på.
  • -script är ett externt program som anropas för varje inkommande hook.

Låt oss ta en närmare titt på några av funktionerna:

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

}

Denna funktion avgör om en begäran om att bekräfta registrering eller en webhook har kommit in. Som följer av dokumentation, om registreringen bekräftas, tas följande Json-struktur emot i Post-förfrågan:

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

Denna fråga måste besvaras:

content-type: application/json

{"signature":«ea3fce4bb15c6de4fec365d36bcebbc34ccddf54616d5ca12e1972f82b6d37af»}

Där signaturen beräknas som:

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

Om en webhook anländer ser strukturen för Post-begäran ut så här:

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öljaktligen, beroende på begäran, måste du förstå hur du behandlar uppgifterna. Jag valde posten som en indikator "Type":"SubscriptionConfirmation", eftersom det finns i begäran om prenumerationsbekräftelse och inte finns i webhook. Baserat på närvaron/frånvaron av denna post i POST-begäran, går vidare exekvering av programmet antingen till funktionen SubscriptionConfirmation, eller in i funktionen GotRecords.

Vi kommer inte att överväga prenumerationsbekräftelsefunktionen i detalj; den implementeras enligt principerna som anges i dokumentation. Du kan se källkoden för denna funktion på projekt git repositories.

GotRecords-funktionen analyserar en inkommande begäran och för varje Record-objekt anropar ett externt skript (vars namn skickades i parametern -script) med parametrarna:

  • hinknamn
  • objektnyckel
  • handling:
    • copy - om i den ursprungliga begäran EventName = ObjectCreated | PutObject | PutObjectCopy
    • delete - om i den ursprungliga begäran EventName = ObjectRemoved | Ta bort objekt

Alltså, om en krok kommer med en Post-förfrågan, enligt beskrivningen ovan, och parametern -script=script.sh så kommer skriptet att anropas enligt följande:

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

Det bör förstås att denna webhook-mottagningsserver inte är en komplett produktionslösning, utan ett förenklat exempel på en möjlig implementering.

Exempel på arbete

Låt oss synkronisera filerna från huvudbucket i MCS till backup-bucket i AWS. Den huvudsakliga hinken heter myfiles-ash, den backup som kallas myfiles-backup (hinkkonfigurationen i AWS ligger utanför ramen för denna artikel). Följaktligen, när en fil placeras i huvudbehållaren, bör dess kopia visas i säkerhetskopian, och när den raderas från den huvudsakliga, bör den tas bort i säkerhetskopian.

Vi kommer att arbeta med hinkar med hjälp av verktyget awscli, som är kompatibelt med både MCS molnlagring och AWS molnlagring.

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

Låt oss konfigurera åtkomst till 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]:

Låt oss konfigurera åtkomst till 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]:

Låt oss kontrollera åtkomsterna:

Till AWS:

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

För MCS, när du kör kommandot måste du lägga till —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

Åtkomst.

Låt oss nu skriva ett skript för att bearbeta den inkommande kroken, låt oss kalla 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

Låt oss starta servern:

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

Låt oss se hur det här fungerar. Genom MCS webbgränssnitt lägg till test.txt-filen i myfiles-ash-hinken. Konsolloggarna visar att en begäran gjordes till webhook-servern:

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

Låt oss kontrollera innehållet i myfiles-backup-bucket 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, genom webbgränssnittet, kommer vi att ta bort filen från myfiles-ash-hinken.

Serverloggar:

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

Hinkens innehåll:

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

Filen raderas, problemet är löst.

Slutsats och Att göra

All kod som används i den här artikeln är i mitt förråd. Det finns också exempel på skript och exempel på räkning av signaturer för registrering av webhooks.

Den här koden är inget annat än ett exempel på hur du kan använda S3 webhooks i dina aktiviteter. Som jag sa i början, om du planerar att använda en sådan server i produktionen måste du åtminstone skriva om servern för asynkront arbete: registrera inkommande webhooks i en kö (RabbitMQ eller NATS), och därifrån analysera dem och bearbeta dem med arbetaransökningar. Annars, när webhooks kommer massivt, kan du stöta på brist på serverresurser för att slutföra uppgifter. Närvaron av köer gör att du kan distribuera servern och arbetarna, samt lösa problem med upprepade uppgifter i händelse av fel. Det är också lämpligt att ändra loggningen till en mer detaljerad och mer standardiserad.

Lycka!

Mer läsning om ämnet:

Källa: will.com

Lägg en kommentar