Príklad aplikácie riadenej udalosťami založenej na webhookoch v úložisku objektov S3 Mail.ru Cloud Solutions

Príklad aplikácie riadenej udalosťami založenej na webhookoch v úložisku objektov S3 Mail.ru Cloud Solutions
Kávovar Rube Goldberg

Architektúra riadená udalosťami zvyšuje nákladovú efektívnosť použitých zdrojov, pretože sa využívajú len v momente, keď sú potrebné. Existuje veľa možností, ako to implementovať a nevytvárať ďalšie cloudové entity ako pracovné aplikácie. A dnes nebudem hovoriť o FaaS, ale o webhookoch. Ukážem výukový príklad spracovania udalostí pomocou webhookov na ukladanie objektov.

Pár slov o ukladaní objektov a webhookoch. Objektové úložisko umožňuje ukladať akékoľvek dáta v cloude vo forme objektov, prístupných cez S3 alebo iné API (v závislosti od implementácie) cez HTTP/HTTPS. Webhooky sú vo všeobecnosti vlastné spätné volania HTTP. Zvyčajne sú spúšťané udalosťou, ako je vloženie kódu do úložiska alebo uverejnenie komentára na blogu. Keď nastane udalosť, pôvodná lokalita odošle požiadavku HTTP na adresu URL zadanú pre webhook. Výsledkom je, že udalosti na jednej stránke spúšťajú akcie na inej (wiki). V prípade, že zdrojovou stránkou je úložisko objektov, udalosti pôsobia ako zmeny v jej obsahu.

Príklady jednoduchých prípadov, kedy je možné použiť takúto automatizáciu:

  1. Vytváranie kópií všetkých objektov v inom cloudovom úložisku. Kópie sa musia vytvárať za chodu pri každom pridávaní alebo zmene súborov.
  2. Automatické vytváranie série miniatúr grafických súborov, pridávanie vodoznakov do fotografií a ďalšie úpravy obrázkov.
  3. Notifikácia o príchode nových dokumentov (napríklad distribuovaná účtovná služba nahráva zostavy do cloudu a finančný monitoring dostáva notifikácie o nových zostavách, kontroluje ich a analyzuje).
  4. O niečo zložitejšie prípady zahŕňajú napríklad vygenerovanie požiadavky pre Kubernetes, ktorý vytvorí pod s potrebnými kontajnermi, odovzdá mu parametre úlohy a po spracovaní kontajner zbalí.

Ako príklad urobíme variant úlohy 1, keď sa zmeny v sektore úložiska objektov Mail.ru Cloud Solutions (MCS) synchronizujú v objektovom úložisku AWS pomocou webhookov. V skutočne zaťaženom prípade by mala byť asynchrónna práca zabezpečená registráciou webhookov vo fronte, ale pre tréningovú úlohu vykonáme implementáciu bez toho.

Pracovná schéma

Interakčný protokol je podrobne opísaný v Sprievodca webhookmi S3 na MCS. Pracovná schéma obsahuje nasledujúce prvky:

  • Vydavateľská služba, ktorý je na strane úložiska S3 a publikuje požiadavky HTTP pri spustení webnhooku.
  • Prijímací server webhooku, ktorá počúva požiadavky od publikačnej služby HTTP a vykonáva príslušné akcie. Server môže byť napísaný v akomkoľvek jazyku, v našom príklade napíšeme server v Go.

Zvláštnosťou implementácie webhookov v S3 API je registrácia prijímacieho servera webhookov na publikačnej službe. Prijímací server webhooku musí predovšetkým potvrdiť odber správ zo služby publikovania (v iných implementáciách webhooku sa potvrdenie odberu zvyčajne nevyžaduje).

V súlade s tým musí prijímací server webhooku podporovať dve hlavné operácie:

  • odpovedať na žiadosť vydavateľskej služby o potvrdenie registrácie,
  • spracovávať prichádzajúce udalosti.

Inštalácia prijímacieho servera webhooku

Na spustenie prijímacieho servera webhooku potrebujete server Linux. V tomto článku ako príklad používame virtuálnu inštanciu, ktorú nasadíme na MCS.

Nainštalujeme potrebný softvér a spustíme prijímací server webhooku.

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

Naklonujte priečinok s prijímacím serverom webhooku:

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.

Spustíme server:

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

Predplaťte si službu publikovania

Váš webhookový prijímací server môžete zaregistrovať cez API alebo webové rozhranie. Pre zjednodušenie sa zaregistrujeme cez webové rozhranie:

  1. Poďme do sekcie vedierka v riadiacej miestnosti.
  2. Prejdite do segmentu, pre ktorý budeme konfigurovať webhooky, a kliknite na ozubené koliesko:

Príklad aplikácie riadenej udalosťami založenej na webhookoch v úložisku objektov S3 Mail.ru Cloud Solutions

Prejdite na kartu Webhooky a kliknite na Pridať:

Príklad aplikácie riadenej udalosťami založenej na webhookoch v úložisku objektov S3 Mail.ru Cloud Solutions
Vyplňte polia:

Príklad aplikácie riadenej udalosťami založenej na webhookoch v úložisku objektov S3 Mail.ru Cloud Solutions

ID — názov webhooku.

Udalosť – ktoré udalosti sa majú prenášať. Nastavili sme prenos všetkých udalostí, ktoré sa vyskytujú pri práci so súbormi (pridávanie a mazanie).

URL — adresa prijímacieho servera webhooku.

Predpona/prípona filtra je filter, ktorý vám umožňuje generovať webhooky iba pre objekty, ktorých názvy zodpovedajú určitým pravidlám. Napríklad, aby webhook spúšťal iba súbory s príponou .png, in Prípona filtra musíte napísať „png“.

V súčasnosti sú na prístup k prijímaciemu serveru webhooku podporované iba porty 80 a 443.

Poďme kliknúť Pridajte háčik a uvidíme nasledovné:

Príklad aplikácie riadenej udalosťami založenej na webhookoch v úložisku objektov S3 Mail.ru Cloud Solutions
dodal Hook.

Prijímací server webhooku zobrazuje vo svojich denníkoch priebeh procesu registrácie háku:

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

Registrácia je dokončená. V ďalšej časti sa bližšie pozrieme na algoritmus fungovania prijímacieho servera webhooku.

Popis prijímacieho servera webhooku

V našom príklade je server napísaný v Go. Pozrime sa na základné princípy jeho fungovania.

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

Zvážte hlavné funkcie:

  • Ping() – cesta, ktorá odpovedá cez URL/ping, najjednoduchšia implementácia sondy živosti.
  • Webhook() – hlavná cesta, obslužný nástroj URL/webhook:
    • potvrdí registráciu do vydavateľskej služby (prejdite na funkciu SubscriptionConfirmation),
    • spracováva prichádzajúce webhooky (funkcia Gorecords).
  • Funkcie HmacSha256 a HmacSha256hex sú implementáciami šifrovacích algoritmov HMAC-SHA256 a HMAC-SHA256 s výstupom ako reťazec hexadecimálnych čísel na výpočet podpisu.
  • main je hlavná funkcia, spracováva parametre príkazového riadku a registruje obsluhu URL.

Parametre príkazového riadku akceptované serverom:

  • -port je port, na ktorom bude server počúvať.
  • -adresa - IP adresa, ktorú bude server počúvať.
  • -script je externý program, ktorý sa volá pre každý prichádzajúci hák.

Pozrime sa bližšie na niektoré funkcie:

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

}

Táto funkcia určuje, či prišla požiadavka na potvrdenie registrácie alebo webhooku. Ako vyplýva z dokumentáciu, ak je registrácia potvrdená, v žiadosti o príspevok sa dostane nasledujúca štruktúra Json:

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

Na tento dotaz je potrebné odpovedať:

content-type: application/json

{"signature":«ea3fce4bb15c6de4fec365d36bcebbc34ccddf54616d5ca12e1972f82b6d37af»}

Kde sa podpis vypočíta takto:

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

Ak príde webhook, štruktúra žiadosti o príspevok vyzerá takto:

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

Preto v závislosti od požiadavky musíte pochopiť, ako spracovať údaje. Ako indikátor som zvolil vstup "Type":"SubscriptionConfirmation", pretože sa nachádza v žiadosti o potvrdenie predplatného a nenachádza sa vo webhooku. Na základe prítomnosti/neprítomnosti tohto záznamu v požiadavke POST prejde ďalšie vykonávanie programu buď do funkcie SubscriptionConfirmationalebo do funkcie GotRecords.

Funkciou SubscriptionConfirmation sa nebudeme podrobne zaoberať, je implementovaná podľa zásad uvedených v dokumentáciu. Zdrojový kód tejto funkcie si môžete pozrieť na projektové git repozitáre.

Funkcia GotRecords analyzuje prichádzajúcu požiadavku a pre každý objekt Record volá externý skript (ktorého názov bol odovzdaný v parametri -script) s parametrami:

  • názov vedra
  • objektový kľúč
  • akcia:
    • copy - ak je v pôvodnej požiadavke EventName = ObjectCreated | PutObject | PutObjectCopy
    • delete - ak je v pôvodnej požiadavke EventName = ObjectRemoved | OdstrániťObjekt

Ak teda príde hák s požiadavkou Post, ako je opísané nada parametrom -script=script.sh sa skript bude volať takto:

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

Malo by byť zrejmé, že tento prijímací server webhooku nie je kompletným produkčným riešením, ale zjednodušeným príkladom možnej implementácie.

Príklad práce

Poďme synchronizovať súbory z hlavného vedra v MCS do záložného vedra v AWS. Hlavný bucket sa nazýva myfiles-ash, záložný sa nazýva myfiles-backup (konfigurácia bucketu v AWS je nad rámec tohto článku). Podľa toho, keď je súbor umiestnený v hlavnom segmente, jeho kópia by sa mala objaviť v záložnom, a keď je vymazaný z hlavného, ​​mal by byť vymazaný v záložnom.

S vedrami budeme pracovať pomocou pomôcky awscli, ktorá je kompatibilná s cloudovým úložiskom MCS aj cloudovým úložiskom AWS.

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

Poďme nakonfigurovať prístup k 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]:

Poďme nakonfigurovať prístup k 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]:

Pozrime sa na prístupy:

Do AWS:

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

V prípade MCS musíte pri spustení príkazu pridať —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

Prístup.

Teraz napíšme skript na spracovanie prichádzajúceho háku, nazvime ho 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

Spustíme server:

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

Pozrime sa, ako to funguje. Cez webové rozhranie MCS pridajte súbor test.txt do vedra myfiles-ash. Protokoly konzoly ukazujú, že na server webhook bola odoslaná požiadavka:

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

Pozrime sa na obsah zásobníka myfiles-backup v 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

Teraz cez webové rozhranie vymažeme súbor z vedra myfiles-ash.

Denníky servera:

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

Obsah vedra:

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

Súbor je odstránený, problém je vyriešený.

Záver a ToDo

Všetok kód použitý v tomto článku je v mojom úložisku. Nechýbajú ani príklady skriptov a príklady počítania podpisov na registráciu webhookov.

Tento kód nie je nič iné ako príklad toho, ako môžete použiť webhooky S3 vo svojich aktivitách. Ako som povedal na začiatku, ak plánujete používať takýto server vo výrobe, musíte server aspoň prepísať na asynchrónnu prácu: zaregistrovať prichádzajúce webhooky do fronty (RabbitMQ alebo NATS) a odtiaľ ich analyzovať a spracovať s pracovnými aplikáciami. V opačnom prípade, keď webhooky prichádzajú masívne, môžete sa stretnúť s nedostatkom serverových zdrojov na dokončenie úloh. Prítomnosť frontov vám umožňuje distribuovať server a pracovníkov, ako aj riešiť problémy s opakujúcimi sa úlohami v prípade zlyhania. Taktiež je vhodné zmeniť protokolovanie na podrobnejšie a štandardizovanejšie.

Good Luck!

Ďalšie čítanie k téme:

Zdroj: hab.com

Pridať komentár