Mfano wa programu inayoendeshwa na tukio kulingana na viboreshaji vya wavuti katika hifadhi ya kitu cha S3 Mail.ru Cloud Solutions

Mfano wa programu inayoendeshwa na tukio kulingana na viboreshaji vya wavuti katika hifadhi ya kitu cha S3 Mail.ru Cloud Solutions
Mashine ya kahawa ya Rube Goldberg

Usanifu unaoendeshwa na matukio huongeza ufanisi wa gharama ya rasilimali zinazotumiwa kwa sababu zinatumika tu wakati zinahitajika. Kuna chaguzi nyingi za jinsi ya kutekeleza hii na sio kuunda vyombo vya ziada vya wingu kama programu za wafanyikazi. Na leo sitazungumza juu ya FaaS, lakini juu ya viboreshaji vya wavuti. Nitaonyesha mfano wa mafunzo ya kushughulikia matukio kwa kutumia vihifadhi vya wavuti.

Maneno machache kuhusu uhifadhi wa kitu na vijiti vya wavuti. Hifadhi ya kitu hukuruhusu kuhifadhi data yoyote kwenye wingu katika mfumo wa vitu, inayoweza kufikiwa kupitia S3 au API nyingine (kulingana na utekelezaji) kupitia HTTP/HTTPS. Webhooks kwa ujumla ni miito maalum ya HTTP. Kwa kawaida huchochewa na tukio, kama vile msimbo kusukumwa kwenye hifadhi au maoni yanayochapishwa kwenye blogu. Tukio linapotokea, tovuti asili hutuma ombi la HTTP kwa URL iliyobainishwa kwa webhook. Kama matokeo, unaweza kufanya matukio kwenye tovuti moja kusababisha vitendo kwenye nyingine (wiki) Katika hali ambapo tovuti chanzo ni hifadhi ya kitu, matukio hufanya kama mabadiliko kwa yaliyomo.

Mifano ya kesi rahisi wakati otomatiki kama hiyo inaweza kutumika:

  1. Kuunda nakala za vitu vyote kwenye hifadhi nyingine ya wingu. Nakala lazima ziundwe kwa urahisi wakati faili zinaongezwa au kubadilishwa.
  2. Uundaji kiotomatiki wa mfululizo wa vijipicha vya faili za picha, kuongeza alama kwenye picha, na marekebisho mengine ya picha.
  3. Arifa kuhusu kuwasili kwa hati mpya (kwa mfano, huduma ya uhasibu iliyosambazwa hupakia ripoti kwenye wingu, na ufuatiliaji wa kifedha hupokea arifa kuhusu ripoti mpya, hukagua na kuzichanganua).
  4. Kesi ngumu zaidi zinahusisha, kwa mfano, kutoa ombi kwa Kubernetes, ambayo huunda ganda na vyombo muhimu, hupitisha vigezo vya kazi kwake, na baada ya usindikaji huanguka chombo.

Kama mfano, tutafanya lahaja ya kazi ya 1, wakati mabadiliko katika ndoo ya kuhifadhi vitu vya Mail.ru Cloud Solutions (MCS) yanasawazishwa katika hifadhi ya kitu cha AWS kwa kutumia vibao vya wavuti. Katika kesi halisi iliyobeba, kazi ya asynchronous inapaswa kutolewa kwa kusajili webhooks kwenye foleni, lakini kwa kazi ya mafunzo tutafanya utekelezaji bila hii.

Mpango wa kazi

Itifaki ya mwingiliano imeelezewa kwa undani katika Mwongozo wa vijiti vya wavuti vya S3 kwenye MCS. Mpango wa kazi una mambo yafuatayo:

  • Huduma ya uchapishaji, ambayo iko kwenye upande wa hifadhi ya S3 na huchapisha maombi ya HTTP wakati webnhook imeanzishwa.
  • Seva ya kupokea mtandao, ambayo husikiliza maombi kutoka kwa huduma ya uchapishaji ya HTTP na kufanya vitendo vinavyofaa. Seva inaweza kuandikwa kwa lugha yoyote; kwa mfano wetu, tutaandika seva katika Go.

Kipengele maalum cha utekelezaji wa webhooks katika API ya S3 ni usajili wa seva ya kupokea mtandao kwenye huduma ya uchapishaji. Hasa, seva ya kupokea mtandao lazima ithibitishe usajili wa ujumbe kutoka kwa huduma ya uchapishaji (katika utekelezaji mwingine wa webhook, uthibitisho wa usajili hauhitajiki).

Ipasavyo, seva ya kupokea mtandao lazima isaidie shughuli kuu mbili:

  • kujibu ombi la huduma ya uchapishaji kuthibitisha usajili,
  • mchakato wa matukio yanayoingia.

Inasakinisha seva ya kupokea ya mtandao

Ili kuendesha seva ya kupokea ya mtandao, unahitaji seva ya Linux. Katika makala haya, kama mfano, tunatumia mfano pepe tunaotumia kwenye MCS.

Hebu tusakinishe programu muhimu na tuzindua seva ya kupokea mtandao.

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

Funga folda na seva ya kupokea ya mtandao:

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.

Wacha tuanze seva:

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

Jiandikishe kwa huduma ya uchapishaji

Unaweza kusajili seva yako ya kupokea kitabu kupitia API au kiolesura cha wavuti. Kwa unyenyekevu, tutajiandikisha kupitia kiolesura cha wavuti:

  1. Hebu tuende kwenye sehemu ya ndoo katika chumba cha kudhibiti.
  2. Nenda kwenye ndoo ambayo tutasanidi vijiti vya wavuti na ubonyeze gia:

Mfano wa programu inayoendeshwa na tukio kulingana na viboreshaji vya wavuti katika hifadhi ya kitu cha S3 Mail.ru Cloud Solutions

Nenda kwenye kichupo cha Webhooks na ubonyeze Ongeza:

Mfano wa programu inayoendeshwa na tukio kulingana na viboreshaji vya wavuti katika hifadhi ya kitu cha S3 Mail.ru Cloud Solutions
Jaza sehemu:

Mfano wa programu inayoendeshwa na tukio kulingana na viboreshaji vya wavuti katika hifadhi ya kitu cha S3 Mail.ru Cloud Solutions

ID - jina la webhook.

Tukio - ni matukio gani ya kusambaza. Tumeweka maambukizi ya matukio yote yanayotokea wakati wa kufanya kazi na faili (kuongeza na kufuta).

URL - anwani ya seva inayopokea mtandao.

Kiambishi kiambishi awali/kiambishi cha kichujio ni kichungi kinachokuruhusu kutoa vijiti vya wavuti kwa vitu ambavyo majina yao yanalingana na sheria fulani. Kwa mfano, ili webhook ianzishe faili zilizo na kiendelezi cha .png pekee, in Kiambishi tamati cha chujio unahitaji kuandika "png".

Kwa sasa, ni bandari 80 na 443 pekee ndizo zinazotumika kufikia seva inayopokea mtandao.

Hebu bonyeza Ongeza ndoano na tutaona yafuatayo:

Mfano wa programu inayoendeshwa na tukio kulingana na viboreshaji vya wavuti katika hifadhi ya kitu cha S3 Mail.ru Cloud Solutions
Hook imeongezwa.

Seva inayopokea ya mtandao inaonyesha katika kumbukumbu zake maendeleo ya mchakato wa usajili wa ndoano:

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

Usajili umekamilika. Katika sehemu inayofuata, tutaangalia kwa karibu algorithm ya uendeshaji wa seva ya kupokea mtandao.

Maelezo ya seva ya kupokea ya mtandao

Katika mfano wetu, seva imeandikwa katika Go. Hebu tuangalie kanuni za msingi za uendeshaji wake.

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

Fikiria kazi kuu:

  • Ping() - njia inayojibu kupitia URL/ping, utekelezaji rahisi zaidi wa uchunguzi wa uhai.
  • Webhook() - njia kuu, URL/kidhibiti cha wavuti:
    • inathibitisha usajili kwenye huduma ya uchapishaji (nenda kwa kipengele cha Uthibitishaji wa Usajili),
    • huchakata vijiti vinavyoingia (kazi ya Gorecords).
  • Vipengele vya HmacSha256 na HmacSha256hex ni utekelezaji wa algoriti za usimbaji HMAC-SHA256 na HMAC-SHA256 zenye towe kama mfuatano wa nambari za heksadesimali kwa kukokotoa saini.
  • kuu ni kazi kuu, huchakata vigezo vya mstari wa amri na kusajili vidhibiti vya URL.

Vigezo vya mstari wa amri vinavyokubaliwa na seva:

  • -port ni bandari ambayo seva itasikiliza.
  • -anwani - Anwani ya IP ambayo seva itasikiliza.
  • -script ni programu ya nje inayoitwa kwa kila ndoano inayoingia.

Wacha tuangalie kwa undani baadhi ya kazi:

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

}

Chaguo hili la kukokotoa huamua ikiwa ombi la kuthibitisha usajili au kiboreshaji cha mtandao limefika. Kama ifuatavyo kutoka nyaraka, ikiwa usajili umethibitishwa, muundo ufuatao wa Json unapokelewa katika ombi la Chapisho:

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

Swali hili linahitaji kujibiwa:

content-type: application/json

{"signature":Β«ea3fce4bb15c6de4fec365d36bcebbc34ccddf54616d5ca12e1972f82b6d37afΒ»}

Ambapo saini imehesabiwa kama:

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

Ikiwa webhook itawasili, muundo wa ombi la Posta unaonekana kama hii:

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

Ipasavyo, kulingana na ombi, unahitaji kuelewa jinsi ya kusindika data. Nilichagua kiingilio kama kiashiria "Type":"SubscriptionConfirmation", kwa kuwa iko katika ombi la uthibitisho wa usajili na haipo kwenye wavuti. Kulingana na uwepo/kutokuwepo kwa ingizo hili katika ombi la POST, utekelezaji zaidi wa programu huenda kwa chaguo la kukokotoa. SubscriptionConfirmation, au kwenye kitendakazi GotRecords.

Hatutazingatia kazi ya Uthibitishaji wa Usajili kwa undani; inatekelezwa kulingana na kanuni zilizowekwa ndani nyaraka. Unaweza kuona msimbo wa chanzo wa chaguo hili la kukokotoa kwenye hazina za mradi wa git.

Chaguo za kukokotoa za GotRecords huchanganua ombi linaloingia na kwa kila kitu cha Rekodi huita hati ya nje (ambayo jina lake lilipitishwa katika -script parameta) na vigezo:

  • jina la ndoo
  • ufunguo wa kitu
  • kitendo:
    • nakala - ikiwa katika ombi asili la EventName = ObjectCreated | Weka Kitu | WekaObjectCopy
    • futa - ikiwa katika ombi la asili la EventName = ObjectRemoved | FutaKitu

Kwa hivyo, ikiwa ndoano inakuja na ombi la Chapisho, kama ilivyoelezewa juu ya, na parameta -script=script.sh basi hati itaitwa kama ifuatavyo:

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

Inapaswa kueleweka kuwa seva hii ya kupokea mtandao sio suluhisho kamili la uzalishaji, lakini ni mfano rahisi wa utekelezaji unaowezekana.

Mfano wa kazi

Wacha tusawazishe faili kutoka kwa ndoo kuu katika MCS hadi ndoo mbadala katika AWS. Ndoo kuu inaitwa myfiles-ash, chelezo inaitwa myfiles-backup (usanidi wa ndoo katika AWS ni zaidi ya upeo wa kifungu hiki). Ipasavyo, faili inapowekwa kwenye ndoo kuu, nakala yake inapaswa kuonekana kwenye chelezo, na inapofutwa kutoka kwa ile kuu, inapaswa kufutwa kwenye chelezo.

Tutafanya kazi na ndoo kwa kutumia matumizi ya awscli, ambayo yanaoana na hifadhi ya wingu ya MCS na hifadhi ya wingu ya 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) ...

Wacha tusanidi ufikiaji wa API ya S3 MCS:

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

Wacha tusanidi ufikiaji wa API ya 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]:

Wacha tuangalie ufikiaji:

kwa AWS ni:

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

Kwa MCS, unapoendesha amri unahitaji kuongeza -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

Imefikiwa.

Sasa wacha tuandike hati ya kusindika ndoano inayoingia, tuiite 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

Wacha tuanze seva:

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

Hebu tuone jinsi inavyofanya kazi. Kupitia Kiolesura cha wavuti cha MCS ongeza faili ya test.txt kwenye ndoo ya myfiles-ash. Kumbukumbu za kiweko zinaonyesha kuwa ombi lilifanywa kwa seva ya wavuti:

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

Wacha tuangalie yaliyomo kwenye ndoo ya chelezo ya myfiles katika 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

Sasa, kupitia kiolesura cha wavuti, tutafuta faili kutoka kwa ndoo ya myfiles-ash.

Kumbukumbu za seva:

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

Yaliyomo kwenye ndoo:

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

Faili imefutwa, tatizo linatatuliwa.

Hitimisho na ToDo

Kanuni zote kutumika katika makala hii ni katika hazina yangu. Pia kuna mifano ya maandishi na mifano ya kuhesabu saini kwa ajili ya kusajili webhooks.

Nambari hii sio zaidi ya mfano wa jinsi unavyoweza kutumia viboreshaji vya wavuti vya S3 katika shughuli zako. Kama nilivyosema mwanzoni, ikiwa unapanga kutumia seva kama hiyo katika uzalishaji, unahitaji angalau kuandika tena seva kwa kazi ya asynchronous: kusajili webhooks zinazoingia kwenye foleni (RabbitMQ au NATS), na kutoka hapo uchanganue na kuzichakata. na maombi ya wafanyikazi. Vinginevyo, vijiti vya wavuti vinapofika kwa wingi, unaweza kukutana na ukosefu wa rasilimali za seva ili kukamilisha kazi. Uwepo wa foleni hukuruhusu kusambaza seva na wafanyikazi, na pia kutatua shida na kazi za kurudia ikiwa utashindwa. Inashauriwa pia kubadilisha ukataji miti kwa maelezo zaidi na ya kawaida zaidi.

Mafanikio!

Kusoma zaidi juu ya mada:

Chanzo: mapenzi.com

Kuongeza maoni