Contoh aplikasi berbasis peristiwa berdasarkan webhook di penyimpanan objek S3 Mail.ru Cloud Solutions

Contoh aplikasi berbasis peristiwa berdasarkan webhook di penyimpanan objek S3 Mail.ru Cloud Solutions
Mesin kopi Rube Goldberg

Arsitektur berbasis peristiwa meningkatkan efisiensi biaya sumber daya yang digunakan karena sumber daya tersebut hanya digunakan pada saat dibutuhkan. Ada banyak pilihan tentang cara menerapkan ini dan tidak membuat entitas cloud tambahan sebagai aplikasi pekerja. Dan hari ini saya tidak akan berbicara tentang FaaS, tetapi tentang webhook. Saya akan menunjukkan contoh tutorial penanganan event menggunakan webhook penyimpanan objek.

Sedikit penjelasan tentang penyimpanan objek dan webhook. Penyimpanan objek memungkinkan Anda menyimpan data apa pun di cloud dalam bentuk objek, dapat diakses melalui S3 atau API lain (tergantung implementasi) melalui HTTP/HTTPS. Webhook umumnya merupakan panggilan balik HTTP khusus. Mereka biasanya dipicu oleh suatu peristiwa, seperti kode yang dimasukkan ke repositori atau komentar yang diposting di blog. Ketika suatu peristiwa terjadi, situs asal mengirimkan permintaan HTTP ke URL yang ditentukan untuk webhook. Hasilnya, Anda dapat membuat peristiwa di satu situs memicu tindakan di situs lain (wiki). Dalam kasus di mana situs sumber adalah tempat penyimpanan objek, peristiwa bertindak sebagai perubahan pada kontennya.

Contoh kasus sederhana ketika otomatisasi tersebut dapat digunakan:

  1. Membuat salinan semua objek di penyimpanan cloud lain. Salinan harus dibuat dengan cepat setiap kali file ditambahkan atau diubah.
  2. Pembuatan otomatis serangkaian thumbnail file grafik, menambahkan tanda air ke foto, dan modifikasi gambar lainnya.
  3. Pemberitahuan kedatangan dokumen baru (misalnya, layanan akuntansi terdistribusi mengunggah laporan ke cloud, dan pemantauan keuangan menerima pemberitahuan tentang laporan baru, memeriksa dan menganalisisnya).
  4. Kasus yang sedikit lebih kompleks melibatkan, misalnya, pembuatan permintaan ke Kubernetes, yang membuat sebuah pod dengan container yang diperlukan, meneruskan parameter tugas ke dalamnya, dan setelah pemrosesan, container tersebut akan diciutkan.

Sebagai contoh, kita akan membuat varian tugas 1, ketika perubahan dalam bucket penyimpanan objek Mail.ru Cloud Solutions (MCS) disinkronkan dalam penyimpanan objek AWS menggunakan webhook. Dalam kasus yang dimuat secara nyata, pekerjaan asinkron harus disediakan dengan mendaftarkan webhook dalam antrian, tetapi untuk tugas pelatihan kami akan melakukan implementasi tanpa ini.

Skema kerja

Protokol interaksi dijelaskan secara rinci di Panduan untuk webhook S3 di MCS. Skema kerja mengandung unsur-unsur berikut:

  • Layanan penerbitan, yang berada di sisi penyimpanan S3 dan menerbitkan permintaan HTTP ketika webnhook dipicu.
  • Server penerima webhook, yang mendengarkan permintaan dari layanan penerbitan HTTP dan melakukan tindakan yang sesuai. Server dapat ditulis dalam bahasa apa pun; dalam contoh kita, kita akan menulis server dalam Go.

Fitur khusus implementasi webhook di S3 API adalah pendaftaran server penerima webhook pada layanan penerbitan. Secara khusus, server penerima webhook harus mengonfirmasi langganan pesan dari layanan penerbitan (dalam implementasi webhook lainnya, konfirmasi berlangganan biasanya tidak diperlukan).

Oleh karena itu, server penerima webhook harus mendukung dua operasi utama:

  • menanggapi permintaan layanan penerbitan untuk mengonfirmasi pendaftaran,
  • memproses kejadian yang masuk.

Memasang server penerima webhook

Untuk menjalankan server penerima webhook, Anda memerlukan server Linux. Pada artikel ini, sebagai contoh, kami menggunakan instance virtual yang kami terapkan di MCS.

Mari instal perangkat lunak yang diperlukan dan luncurkan server penerima 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) ...

Kloning 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 servernya:

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

Berlangganan ke layanan penerbitan

Anda dapat mendaftarkan server penerima webhook Anda melalui API atau antarmuka web. Untuk mempermudah, kami akan mendaftar melalui antarmuka web:

  1. Mari kita pergi ke bagian ember di ruang kendali.
  2. Buka bucket yang akan kita konfigurasikan webhooknya dan klik roda gigi:

Contoh aplikasi berbasis peristiwa berdasarkan webhook di penyimpanan objek S3 Mail.ru Cloud Solutions

Buka tab Webhook dan klik Tambahkan:

Contoh aplikasi berbasis peristiwa berdasarkan webhook di penyimpanan objek S3 Mail.ru Cloud Solutions
Isi kolom:

Contoh aplikasi berbasis peristiwa berdasarkan webhook di penyimpanan objek S3 Mail.ru Cloud Solutions

ID β€” nama webhook.

Acara - peristiwa mana yang akan dikirimkan. Kami telah mengatur transmisi semua peristiwa yang terjadi saat bekerja dengan file (menambah dan menghapus).

URL β€” alamat server penerima webhook.

Filter awalan/akhiran adalah filter yang memungkinkan Anda membuat webhook hanya untuk objek yang namanya sesuai dengan aturan tertentu. Misalnya, agar webhook hanya memicu file dengan ekstensi .png, di Filter akhiran Anda perlu menulis "png".

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

Ayo klik Tambahkan kait dan kita akan melihat yang berikut ini:

Contoh aplikasi berbasis peristiwa berdasarkan webhook di penyimpanan objek S3 Mail.ru Cloud Solutions
Kait menambahkan.

Server penerima webhook menunjukkan dalam lognya kemajuan proses pendaftaran hook:

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. Di bagian selanjutnya, kita akan melihat lebih dekat algoritma pengoperasian server penerima webhook.

Deskripsi server penerima webhook

Dalam contoh kita, server ditulis dalam Go. Mari kita lihat prinsip dasar pengoperasiannya.

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

Pertimbangkan fungsi utama:

  • Ping() - rute yang merespons melalui URL/ping, implementasi paling sederhana dari pemeriksaan keaktifan.
  • Webhook() - rute utama, pengendali URL/webhook:
    • mengonfirmasi pendaftaran pada layanan penerbitan (buka fungsi Konfirmasi Langganan),
    • memproses webhook yang masuk (fungsi Gorecords).
  • Fungsi HmacSha256 dan HmacSha256hex merupakan implementasi algoritma enkripsi HMAC-SHA256 dan HMAC-SHA256 dengan keluaran berupa rangkaian angka heksadesimal untuk menghitung tanda tangan.
  • main adalah fungsi utama, memproses parameter baris perintah dan mendaftarkan penangan URL.

Parameter baris perintah yang diterima oleh server:

  • -port adalah port tempat server akan mendengarkan.
  • -alamat - Alamat IP yang akan didengarkan server.
  • -script adalah program eksternal yang dipanggil untuk setiap hook masuk.

Mari kita lihat lebih dekat beberapa fungsinya:

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

}

Fungsi ini menentukan apakah permintaan konfirmasi pendaftaran atau webhook telah tiba. Sebagai berikut dari dokumentasi, jika pendaftaran dikonfirmasi, struktur Json berikut diterima dalam permintaan Post:

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

Pertanyaan ini perlu dijawab:

content-type: application/json

{"signature":Β«ea3fce4bb15c6de4fec365d36bcebbc34ccddf54616d5ca12e1972f82b6d37afΒ»}

Dimana tanda tangan dihitung sebagai:

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

Jika webhook tiba, struktur permintaan Post akan 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"
            }
        }
    ]
}

Oleh karena itu, bergantung pada permintaannya, Anda perlu memahami cara memproses data. Saya memilih entri sebagai indikator "Type":"SubscriptionConfirmation", karena ada dalam permintaan konfirmasi berlangganan dan tidak ada di webhook. Berdasarkan ada/tidaknya entri ini dalam permintaan POST, eksekusi program selanjutnya akan menuju ke fungsi tersebut SubscriptionConfirmation, atau menjadi suatu fungsi GotRecords.

Kami tidak akan mempertimbangkan fungsi Konfirmasi Langganan secara rinci; ini diterapkan sesuai dengan prinsip yang ditetapkan dalam dokumentasi. Anda dapat melihat kode sumber untuk fungsi ini di repositori proyek git.

Fungsi GotRecords mem-parsing permintaan masuk dan untuk setiap objek Record memanggil skrip eksternal (yang namanya diteruskan dalam parameter -script) dengan parameter:

  • nama ember
  • kunci objek
  • tindakan:
    • salin - jika dalam permintaan asli EventName = ObjectCreated | Letakkan Objek | Letakkan ObjekSalinan
    • hapus - jika dalam permintaan asli EventName = ObjectRemoved | Hapus Objek

Jadi, jika sebuah hook datang dengan permintaan Post, seperti yang dijelaskan atas, dan parameter -script=script.sh maka skrip akan dipanggil sebagai berikut:

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

Perlu dipahami bahwa server penerima webhook ini bukanlah solusi produksi lengkap, namun contoh sederhana dari kemungkinan implementasi.

Contoh pekerjaan

Mari sinkronkan file dari bucket utama di MCS ke bucket cadangan di AWS. Bucket utama disebut myfiles-ash, bucket cadangan disebut myfiles-backup (konfigurasi bucket di AWS berada di luar cakupan artikel ini). Oleh karena itu, ketika sebuah file ditempatkan di keranjang utama, salinannya akan muncul di keranjang cadangan, dan ketika dihapus dari keranjang utama, salinannya harus dihapus di keranjang cadangan.

Kami akan bekerja dengan bucket menggunakan utilitas awscli, yang kompatibel dengan penyimpanan cloud MCS dan penyimpanan cloud 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 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 konfigurasikan akses ke API AWS S3:

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

Ke AWS:

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

Untuk MCS, saat menjalankan perintah Anda perlu menambahkan β€”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 tulis skrip untuk memproses hook 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=""
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

Mari kita mulai servernya:

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

Mari kita lihat cara kerjanya. Melalui Antarmuka web MCS tambahkan file test.txt ke keranjang myfiles-ash. Log konsol menunjukkan 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 bucket 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, kami akan menghapus 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

Isi ember:

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

File dihapus, masalahnya terpecahkan.

Kesimpulan dan Hal yang Harus Dilakukan

Semua kode yang digunakan dalam artikel ini adalah di repositori saya. Terdapat juga contoh script dan contoh penghitungan tanda tangan untuk mendaftarkan webhook.

Kode ini tidak lebih dari contoh bagaimana Anda dapat menggunakan webhook S3 dalam aktivitas Anda. Seperti yang saya katakan di awal, jika Anda berencana untuk menggunakan server seperti itu dalam produksi, Anda setidaknya perlu menulis ulang server untuk pekerjaan asinkron: daftarkan webhook yang masuk dalam antrian (RabbitMQ atau NATS), dan dari sana parsing dan proseskan dengan aplikasi pekerja. Jika tidak, ketika webhook hadir dalam jumlah besar, Anda mungkin mengalami kekurangan sumber daya server untuk menyelesaikan tugas. Kehadiran antrian memungkinkan Anda untuk mendistribusikan server dan pekerja, serta memecahkan masalah dengan pengulangan tugas jika terjadi kegagalan. Disarankan juga untuk mengubah logging ke yang lebih detail dan terstandarisasi.

Good Luck!

Bacaan lebih lanjut tentang topik ini:

Sumber: www.habr.com

Tambah komentar