Contoh aplikasi berbasis webhook yang digerakkan oleh peristiwa dalam penyimpanan objek S3 Mail.ru Cloud Solutions



Mesin kopi Rube Goldberg



Arsitektur berbasis-event meningkatkan efisiensi biaya dari sumber daya yang digunakan karena hanya digunakan saat dibutuhkan. Ada banyak opsi untuk bagaimana mengimplementasikan ini dan tidak membuat entitas cloud tambahan sebagai aplikasi pekerja. Dan hari ini saya tidak akan berbicara tentang FaaS, tetapi tentang webhooks. Saya akan menunjukkan kepada Anda contoh tutorial menangani acara dengan webhooks Penyimpanan Objek.



Beberapa kata tentang penyimpanan objek dan webhooks. Penyimpanan objek memungkinkan Anda untuk menyimpan data apa pun di cloud sebagai objek yang dapat diakses melalui S3 atau API lain (tergantung implementasinya) melalui HTTP / HTTPS. Webhook umumnya adalah panggilan balik HTTP khusus. Mereka biasanya dipicu oleh suatu peristiwa, seperti pengiriman kode ke repositori atau komentar yang diposting di blog. Ketika suatu peristiwa terjadi, situs asal mengirimkan permintaan HTTP ke URL yang ditentukan untuk webhook. Akibatnya, Anda dapat membuat acara di satu situs memicu tindakan di situs lain ( wiki ). Ketika situs sumbernya adalah Object Storage, peristiwa itu berubah ke isinya.



Contoh kasus sederhana ketika otomatisasi tersebut dapat digunakan:



  1. . « », .
  2. , , .
  3. ( , , , ).
  4. , , Kubernetes, , .


Sebagai contoh, kita akan membuat varian tugas 1, ketika perubahan dalam ember penyimpanan objek Mail.ru Cloud Solutions (MCS) disinkronkan menggunakan webhooks dalam penyimpanan objek AWS. Dalam kasus nyata yang dimuat, Anda harus menyediakan pekerjaan asinkron dengan mendaftarkan kait web dalam antrian, tetapi untuk tugas pendidikan kami akan melakukan implementasi tanpa ini.



Skema kerja



Protokol komunikasi dijelaskan secara rinci dalam panduan webhook S3 di MCS . Skema pekerjaan memiliki unsur-unsur berikut:



  • Layanan penerbitan yang duduk di sisi S3 dan menerbitkan permintaan HTTP saat webnhook menyala.
  • Server penerima webhook yang mendengarkan permintaan dari layanan penerbitan HTTP dan mengambil tindakan yang sesuai. Server dapat ditulis dalam bahasa apa pun, dalam contoh kami, kami akan menulis server di Go.


Kekhasan implementasi webhook di API S3 adalah pendaftaran server penerimaan webhook pada layanan publikasi. Secara khusus, server penerima webhook harus mengkonfirmasi berlangganan pesan layanan penerbitan (dalam implementasi webhook lainnya, berlangganan biasanya tidak diperlukan).



Karenanya, server penerima webhook harus mendukung dua operasi utama:



  • menanggapi permintaan dari layanan publikasi untuk konfirmasi pendaftaran,
  • memproses acara yang masuk.


Menginstal server untuk menerima kait web



Untuk menjalankan server penerima webhook, Anda memerlukan server Linux. Dalam artikel ini, sebagai contoh, kami menggunakan contoh virtual yang kami sebarkan ke MCS.



Instal perangkat lunak yang diperlukan dan luncurkan server webhook.



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 folder dengan server penerima webhook:



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.


Mari kita mulai server:



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


Berlangganan layanan penerbitan



Anda dapat mendaftarkan server Anda untuk menerima kait web melalui API atau antarmuka web. Untuk kesederhanaan, kami akan mendaftar melalui antarmuka web:



  1. Pergi ke bagian ember di ruang kontrol.
  2. Kami pergi ke ember, di mana kami akan mengatur webhooks, dan klik gear:






Buka tab Webhooks dan klik Add:





Isi kolom:







ID - nama webhook.



Acara - acara apa yang akan dikirim. Kami telah mengatur transfer semua peristiwa yang terjadi saat bekerja dengan file (menambah dan menghapus).



URL - alamat server penerima webhook.



Filter prefix / suffix adalah filter yang memungkinkan pembuatan webhook hanya untuk objek yang namanya cocok dengan aturan tertentu. Misalnya, untuk membuat file webhook hanya berfungsi dengan ekstensi .png, tulis "png" di akhiran Filter .



Saat ini, hanya port 80 dan 443 yang didukung untuk mengakses server penerima webhook.



Klik Tambah kait dan lihat yang berikut:





Kait ditambahkan.



Server untuk menerima kait web di log menunjukkan progres proses pendaftaran kait:



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


Pendaftaran selesai. Pada bagian selanjutnya, kita akan melihat lebih dekat pada algoritma untuk server yang menerima webhook.



Deskripsi server untuk menerima kait web



Dalam contoh kami, server ditulis dalam Go. Mari kita menganalisis prinsip-prinsip dasar kerjanya.



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 %s\n", req.Method, req.RemoteAddr)
    fmt.Fprintf(w, "Pong\n")
}

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


Mari kita perhatikan fungsi-fungsi utamanya:



  • Ping () adalah rute yang merespons dengan URL / ping, implementasi paling sederhana dari penyelidikan liveness.
  • Webhook () - rute utama, penangan URL / webhook:

    • mengonfirmasi pendaftaran pada layanan penerbitan (transisi ke fungsi SubscriptionConfirmation),
    • memproses kait web masuk (fungsi Gotrecords).
  • Fungsi HmacSha256 dan HmacSha256hex adalah implementasi dari algoritma enkripsi HMAC-SHA256 dan HMAC-SHA256 dengan output sebagai string angka heksadesimal untuk pengurangan tanda tangan.
  • utama adalah fungsi utama, memproses parameter baris perintah dan mendaftarkan penangan URL.


Parameter baris perintah yang diterima oleh server:



  • -port adalah port di mana server akan mendengarkan.
  • - alamat adalah alamat IP yang akan didengarkan server.
  • -script adalah program eksternal yang dipanggil pada setiap hook yang masuk.


Mari kita lihat lebih dekat beberapa fungsi:



//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 %s\n", 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)
    }

}


Fungsi ini menentukan apa yang telah datang - permintaan untuk konfirmasi pendaftaran atau webhook. Sebagai berikut dari dokumentasi , dalam hal konfirmasi pendaftaran, struktur Json berikut datang dalam permintaan Pos:



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


Permintaan ini perlu dijawab:



content-type: application/json

{"signature":«ea3fce4bb15c6de4fec365d36bcebbc34ccddf54616d5ca12e1972f82b6d37af»}


Di mana tanda tangan dihitung sebagai:



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


Jika webhook tiba, maka struktur permintaan Posting terlihat seperti ini:



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


Karena itu, tergantung pada permintaan, Anda perlu memahami cara memproses data. Saya memilih catatan sebagai indikator "Type":"SubscriptionConfirmation", karena ada dalam permintaan untuk mengkonfirmasi berlangganan dan tidak ada di webhook. Berdasarkan ada / tidaknya catatan ini dalam permintaan POST, eksekusi program lebih lanjut dapat berupa fungsi SubscriptionConfirmationatau fungsi GotRecords.



Kami tidak akan mempertimbangkan fungsi SubscriptionConfirmation secara terperinci, ini dilaksanakan sesuai dengan prinsip-prinsip yang ditetapkan dalam dokumentasi . Anda dapat memeriksa kode sumber untuk fungsi ini di repositori git proyek .



Fungsi GotRecords mem-parsing permintaan masuk dan, untuk setiap objek Rekam, memanggil skrip eksternal (yang namanya dilewatkan dalam parameter skrip) dengan parameter:



  • nama ember
  • kunci objek
  • bertindak:

    • salin - jika dalam permintaan asli EventName = ObjectCreated | PutObject | PutObjectCopy
    • hapus - jika dalam permintaan asli EventName = ObjectRemoved | DeleteObject


Jadi, jika sebuah kail dengan permintaan Posting tiba, seperti yang dijelaskan di atas , dan parameter -script = script.sh, maka skrip akan dipanggil sebagai berikut:



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


Harus dipahami bahwa server penerima webhook ini bukan solusi produksi yang lengkap, tetapi contoh sederhana dari implementasi yang mungkin.



Contoh pekerjaan



Mari menyinkronkan file ember utama di MCS ke ember cadangan di AWS. Ember utama disebut myfiles-ash, cadangannya adalah myfiles-backup (mengonfigurasi ember di AWS berada di luar ruang lingkup artikel ini). Dengan demikian, ketika file ditempatkan di ember utama, salinannya akan muncul di cadangan, ketika dihapus dari yang utama, itu harus dihapus di cadangan.



Kami akan bekerja dengan bucket menggunakan utilitas awscli, yang kompatibel dengan penyimpanan cloud MCS dan penyimpanan 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) ...


Mari kita konfigurasikan akses ke 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]:


Mari kita konfigurasikan akses ke 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]:


Mari kita periksa akses:



Untuk AWS:



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


Untuk MCS, ketika perintah sedang berjalan, tambahkan --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


Diakses.



Sekarang mari kita menulis skrip untuk menangani kail yang masuk, sebut saja 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


Kami memulai server:



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


Lihat cara kerjanya. Melalui antarmuka web, MCS menambahkan file test.txt ke dalam ember myfiles-ash. Di log di konsol, Anda dapat melihat bahwa permintaan dibuat ke server webhook:



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


Mari kita periksa isi ember myfiles-backup di 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


Sekarang, melalui antarmuka web, hapus file dari ember myfiles-ash.



Log server:



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


Konten ember:



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


File dihapus, masalah terpecahkan.



Kesimpulan dan Yang Harus Dilakukan



Semua kode yang digunakan dalam artikel ini ada di repositori saya . Ada juga contoh skrip dan contoh penghitungan tanda tangan untuk mendaftarkan webhooks.



Kode ini tidak lebih dari contoh bagaimana Anda dapat menggunakan S3 webhooks dalam aktivitas Anda. Seperti yang saya katakan di awal, jika Anda berencana untuk menggunakan server seperti itu dalam produksi, Anda setidaknya harus menulis ulang server untuk pekerjaan asinkron: mendaftarkan webhook yang masuk dalam antrian (RabbitMQ atau NATS), dan dari sana membongkar dan memprosesnya dengan aplikasi pekerja. Jika tidak, dengan kedatangan webhook yang masif, Anda mungkin menemui kekurangan sumber daya server untuk melakukan tugas. Kehadiran antrian memungkinkan Anda untuk menyebarkan server dan pekerja, serta memecahkan masalah dengan pengulangan tugas jika terjadi kegagalan. Juga diinginkan untuk mengubah logging ke yang lebih rinci dan lebih terstandarisasi.



Semoga berhasil!



Baca lebih lanjut tentang topik ini:






All Articles