Hamba? Bash! Dibana nomsebenzisi weqokobhe (uphononongo kunye nengxelo yevidiyo evela KubeCon EU'2020)

Kulo nyaka, inkomfa ephambili ye-Kubernetes yaseYurophu-KubeCon + CloudNativeCon Europe 2020-yayinyanisekile. Noko ke, olo tshintsho lwendlela aluzange lusithintele ekunikeleni ingxelo yethu ekukudala siyiceba ethi β€œHamba? Bash! Dibana ne-Shell-operator” ezinikele kwiprojekthi yethu yoMthombo oVulekileyo iqokobhe-umqhubi.

Eli nqaku, liphefumlelwe yintetho, libonisa indlela yokwenza lula inkqubo yokudala abaqhubi be-Kubernetes kwaye ibonisa indlela ongayenza ngayo ngomzamo omncinci usebenzisa i-shell-operator.

Hamba? Bash! Dibana nomsebenzisi weqokobhe (uphononongo kunye nengxelo yevidiyo evela KubeCon EU'2020)

Ukwazisa ividiyo yengxelo (~ imizuzu engama-23 ngesiNgesi, inolwazi olungakumbi kunenqaku) kunye nesicatshulwa esiphambili esivela kuyo kwifom yombhalo. Hamba!

EFlant sihlala silungiselela kwaye sizenzela yonke into. Namhlanje siza kuthetha ngenye ingcamango enomdla. Ukudibana: cloud-native iqokobhe loshicilelo!

Nangona kunjalo, masiqale ngomxholo apho konke oku kwenzekayo: Kubernetes.

Kubernetes API kunye nabalawuli

I-API kwi-Kubernetes inokumelwa njengohlobo lomncedisi wefayile kunye nabalawuli kuhlobo ngalunye lwento. Izinto (izixhobo) kulo mncedisi zimelwe ziifayile ze-YAML. Ukongeza, iseva ine-API esisiseko ekuvumela ukuba wenze izinto ezintathu:

  • fumana ubutyebi ngokohlobo namagama;
  • tshintsha isibonelelo (kule meko, umncedisi ugcina kuphela izinto "ezichanekileyo" - zonke ezenziwe ngokungalunganga okanye ezijoliswe kwezinye izikhokelo zilahliwe);
  • umkhondo kwisixhobo (kule meko, umsebenzisi ufumana ngokukhawuleza inguqulelo yakhe yangoku/ehlaziyiweyo).

Ke, i-Kubernetes isebenza njengohlobo lweseva yefayile (ye-YAML ibonakalisa) ngeendlela ezintathu ezisisiseko (ewe, zikho ezinye, kodwa siya kuzishiya okwangoku).

Hamba? Bash! Dibana nomsebenzisi weqokobhe (uphononongo kunye nengxelo yevidiyo evela KubeCon EU'2020)

Ingxaki kukuba umncedisi unokugcina ulwazi kuphela. Ukuze usebenze udinga umlawuli -yesibini eyona nto ibalulekileyo kunye neyona nto ibalulekileyo kwihlabathi le-Kubernetes.

Kukho iindidi ezimbini eziphambili zabalawuli. Eyokuqala ithatha ulwazi oluvela ku-Kubernetes, luyiqhube ngokwengqiqo yendlwane, kwaye iyibuyisele kwii-K8s. Owesibini uthatha ulwazi oluvela ku-Kubernetes, kodwa, ngokungafaniyo nohlobo lokuqala, utshintsha imeko yezinye izixhobo zangaphandle.

Makhe sijonge ngakumbi kwinkqubo yokudala ukusasazwa eKubernetes:

  • UMlawuli wokusasaza (ubandakanyiwe kwi kube-controller-manager) ifumana ulwazi malunga nokusasazwa kwaye yenze iReplicaSet.
  • I-ReplicaSet yenza iireplicas ezimbini (iipod ezimbini) ngokusekwe kolu lwazi, kodwa ezi pod ayikacwangciswanga.
  • Umcwangcisi ucwangcisa iipod kwaye wongeza ulwazi lweenodi kwiiYAML zabo.
  • I-Kubelets yenza utshintsho kwisixhobo sangaphandle (uthi iDocker).

Emva koko lonke olu landelelwano luphindwa ngokulandelelana: i-kubelet ijonga izikhongozeli, ibala ubume bepod kwaye iyithumele kwakhona. Isilawuli seReplicaSet sifumana ubume kwaye sihlaziye imeko yeseti yokukopisha. Kwenzeka into efanayo kunye noMlawuli woThutho kwaye umsebenzisi ekugqibeleni ufumana imeko ehlaziyiweyo (yangoku).

Hamba? Bash! Dibana nomsebenzisi weqokobhe (uphononongo kunye nengxelo yevidiyo evela KubeCon EU'2020)

Shell-opharetha

Kuvela ukuba i-Kubernetes isekelwe kumsebenzi odibeneyo wabalawuli abahlukeneyo (abaqhubi be-Kubernetes nabo bangabalawuli). Umbuzo uphakama, ungayenza njani eyakho i-opharetha ngomzamo omncinci? Kwaye nantsi leyo esiyiphuhlisileyo iza kuhlangula iqokobhe-umqhubi. Ivumela abalawuli benkqubo ukuba benze ezabo iingxelo besebenzisa iindlela eziqhelekileyo.

Hamba? Bash! Dibana nomsebenzisi weqokobhe (uphononongo kunye nengxelo yevidiyo evela KubeCon EU'2020)

Umzekelo olula: ukukopa iimfihlo

Makhe sijonge umzekelo olula.

Masithi sineqela leKubernetes. Inesithuba samagama default ngeMfihlo ethile mysecret. Ukongeza, kukho ezinye izithuba zamagama kwiqela. Ezinye zazo zineleyibhile ethile encanyathiselwe kuzo. Injongo yethu kukukopa iMfihlo kwizithuba zamagama ezinelebhile.

Umsebenzi untsonkothekile kukuba izithuba ezintsha zamagama zinokuvela kwiqela, kwaye ezinye zazo zinokuba nale lebhile. Kwelinye icala, xa ileyibhile icinyiwe, iMfihlo nayo kufuneka icinywe. Ukongeza kule nto, iMfihlo ngokwayo inokutshintsha kwakhona: kule meko, iMfihlo entsha kufuneka ikopishwe kuzo zonke izithuba zamagama ezineelebhile. Ukuba iMfihlo icinywe ngempazamo kuyo nayiphi na indawo yamagama, umsebenzisi wethu kufuneka ayibuyisele ngoko nangoko.

Ngoku ekubeni umsebenzi uqulunqwe, lixesha lokuba uqale ukuwuphumeza usebenzisa i-shell-operator. Kodwa okokuqala kukufanelekile ukuthetha amagama ambalwa malunga ne-shell-operator ngokwayo.

Indlela i-shell-operator isebenza ngayo

Njengeminye imithwalo yemisebenzi e-Kubernetes, i-shell-operator isebenza kwi-pod yayo. Kule pod kulawulo /hooks iifayile eziphunyeziweyo zigcinwe. Ezi zinokuba zizikripthi kwiBash, Python, Ruby, njl. Sibiza ezo fayile eziphunyezwayo ngamagwegwe (iikhonkco).

Hamba? Bash! Dibana nomsebenzisi weqokobhe (uphononongo kunye nengxelo yevidiyo evela KubeCon EU'2020)

I-Shell-operator ibhalisela iminyhadala ye-Kubernetes kwaye iqhuba ezi hook ngokuphendula kwezo ziganeko sizidingayo.

Hamba? Bash! Dibana nomsebenzisi weqokobhe (uphononongo kunye nengxelo yevidiyo evela KubeCon EU'2020)

I-shell-operator iyazi njani ukuba yeyiphi ihuku emayiqhutywe kwaye nini? Ingongoma kukuba ikhonkco ngalinye linezigaba ezibini. Ngexesha lokuqalisa, i-shell-operator iqhuba onke amagwegwe ngengxabano --config Eli linqanaba loqwalaselo. Kwaye emva kwayo, izikhonkwane ziqaliswa ngendlela eqhelekileyo - ekuphenduleni iziganeko eziqhotyoshelwe kuzo. Kwimeko yokugqibela, ihuku ifumana umxholo obophayo (umxholo obophelelayo) - idatha kwifomathi ye-JSON, esiza kuthetha ngayo ngokubanzi ngezantsi.

Ukwenza umsebenzisi kwi-Bash

Ngoku sikulungele ukuphunyezwa. Ukwenza oku, kufuneka sibhale imisebenzi emibini (ngendlela, sincoma ithala leencwadi iqokobhe_lib, eyenza lula kakhulu iigwegwe zokubhala kwiBash):

  • eyokuqala iyadingeka kwisigaba soqwalaselo - ibonisa umxholo wokubopha;
  • eyesibini iqulethe ingqiqo ephambili yehuka.

#!/bin/bash

source /shell_lib.sh

function __config__() {
  cat << EOF
    configVersion: v1
    # BINDING CONFIGURATION
EOF
}

function __main__() {
  # THE LOGIC
}

hook::run "$@"

Inyathelo elilandelayo kukugqiba ukuba zeziphi izinto esizidingayo. Kwimeko yethu, kufuneka silandelele:

  • imfihlelo yomthombo wotshintsho;
  • zonke izithuba zamagama kwiqela, ukuze wazi ukuba zeziphi ezineleyibhile encanyathiselwe kuzo;
  • iimfihlo ekujoliswe kuzo zokuqinisekisa ukuba zonke zihambelana nemfihlo yomthombo.

Bhalisa kumthombo oyimfihlo

Ukubophelela ubumbeko kuyo kulula kakhulu. Sibonisa ukuba sinomdla kwiMfihlo ngegama mysecret kwindawo yamagama default:

Hamba? Bash! Dibana nomsebenzisi weqokobhe (uphononongo kunye nengxelo yevidiyo evela KubeCon EU'2020)

function __config__() {
  cat << EOF
    configVersion: v1
    kubernetes:
    - name: src_secret
      apiVersion: v1
      kind: Secret
      nameSelector:
        matchNames:
        - mysecret
      namespace:
        nameSelector:
          matchNames: ["default"]
      group: main
EOF

Ngenxa yoko, ikhonkco iya kuqhutywa xa imfihlo yemvelaphi itshintsha (src_secret) kwaye ufumane umxholo obophelelayo ulandelayo:

Hamba? Bash! Dibana nomsebenzisi weqokobhe (uphononongo kunye nengxelo yevidiyo evela KubeCon EU'2020)

Njengoko ubona, iqulethe igama kunye nento yonke.

Ukugcina umkhondo wezithuba zamagama

Ngoku kufuneka ubhalise kwizithuba zamagama. Ukwenza oku, sikhankanya olu lungelelwaniso lulandelayo olubophelelayo:

- name: namespaces
  group: main
  apiVersion: v1
  kind: Namespace
  jqFilter: |
    {
      namespace: .metadata.name,
      hasLabel: (
       .metadata.labels // {} |  
         contains({"secret": "yes"})
      )
    }
  group: main
  keepFullObjectsInMemory: false

Njengoko ubona, indawo entsha ivele kuqwalaselo kunye negama jqFilter. Njengoko igama layo libonisa, jqFilter lihluza lonke ulwazi olungeyomfuneko kwaye lidale into entsha ye-JSON enemihlaba enomdla kuthi. Ihuku enobumbeko olufanayo iya kufumana umxholo obophelelayo ulandelayo:

Hamba? Bash! Dibana nomsebenzisi weqokobhe (uphononongo kunye nengxelo yevidiyo evela KubeCon EU'2020)

Iqulethe uluhlu filterResults kwisithuba samagama ngasinye kwiqela. Ukuguquguquka kweBoolean hasLabel ibonisa ukuba ileyibhile incanyathiselwe kwindawo yegama elinikiweyo. Umkhethi keepFullObjectsInMemory: false ibonisa ukuba akukho mfuneko yokugcina izinto ezipheleleyo kwinkumbulo.

Ukulandelela iimfihlo ekujoliswe kuzo

Sibhalisa kuzo zonke iiMfihlo ezinenkcazo echaziweyo managed-secret: "yes" (ezi ziinjongo zethu dst_secrets):

- name: dst_secrets
  apiVersion: v1
  kind: Secret
  labelSelector:
    matchLabels:
      managed-secret: "yes"
  jqFilter: |
    {
      "namespace":
        .metadata.namespace,
      "resourceVersion":
        .metadata.annotations.resourceVersion
    }
  group: main
  keepFullObjectsInMemory: false

Kule meko jqFilter lihluza lonke ulwazi ngaphandle kwesithuba segama kunye neparameter resourceVersion. Ipharamitha yokugqibela yadluliselwa kwisichasiselo xa udala imfihlo: ikuvumela ukuba uthelekise iinguqulelo zeemfihlo kwaye uzigcine zihlaziyiwe.

Ihuku eqwalaselwe ngolu hlobo iya kuthi, xa iphunyeziwe, ifumane iimeko ezintathu ezibophelelayo ezichazwe ngasentla. Banokucingwa njengohlobo lomfanekiso okhawulezayo (ngokucacileyo) iqela.

Hamba? Bash! Dibana nomsebenzisi weqokobhe (uphononongo kunye nengxelo yevidiyo evela KubeCon EU'2020)

Ngokusekelwe kulo lonke olu lwazi, i-algorithm esisiseko inokuphuhliswa. Iphindaphinda kuzo zonke izithuba zamagama kwaye:

  • ukuba hasLabel imicimbi true kwisithuba samagama sangoku:
    • uthelekisa imfihlo yehlabathi kunye neyasekhaya:
      • ukuba ziyafana, azenzi nto;
      • ukuba ziyahluka - zenza kubectl replace okanye create;
  • ukuba hasLabel imicimbi false kwisithuba samagama sangoku:
    • iqinisekisa ukuba iMfihlo ayikho kwindawo yamagama enikiweyo:
      • ukuba iMfihlo yendawo ikhona, yicime usebenzisa kubectl delete;
      • ukuba iMfihlo yendawo ayibonwa, ayenzi nto.

Hamba? Bash! Dibana nomsebenzisi weqokobhe (uphononongo kunye nengxelo yevidiyo evela KubeCon EU'2020)

Ukuphunyezwa kwe-algorithm kwi-Bash unokukhuphela kwinqaku lethu iindawo zokugcina ezinemizekelo.

Yiloo ndlela esiye sakwazi ngayo ukwenza isilawuli esilula se-Kubernetes sisebenzisa iilayini ezingama-35 ze-YAML config kwaye malunga nenani elifanayo lekhowudi yeBash! Umsebenzi we-shell-operator kukuzidibanisa.

Nangona kunjalo, ukukopa iimfihlo ayisiyiyo kuphela indawo yesicelo sosetyenziso. Nantsi eminye imizekelo embalwa yokubonisa oko akwaziyo ukukwenza.

Umzekelo 1: Ukwenza utshintsho kwiConfigMap

Makhe sijonge ukusasazwa okubandakanya iipod ezintathu. IiPod zisebenzisa iConfigMap ukugcina ulungelelwaniso. Xa ii-pods zazisungulwa, i-ConfigMap yayikwimo ethile (masiyibize v.1). Ngokufanelekileyo, zonke iipods zisebenzisa olu guqulelo oluthile lweConfigMap.

Ngoku masicinge ukuba iConfigMap itshintshile (v.2). Nangona kunjalo, ii-pods ziyakusebenzisa uguqulelo lwangaphambili lwe-ConfigMap (v.1):

Hamba? Bash! Dibana nomsebenzisi weqokobhe (uphononongo kunye nengxelo yevidiyo evela KubeCon EU'2020)

Ndingazifumana njani ukuba zitshintshele kwiConfigMap entsha (v.2)? Impendulo ilula: sebenzisa itemplate. Masidibanise inkcazo ye-checksum kwicandelo template Ulungelelwaniso lokusasazwa:

Hamba? Bash! Dibana nomsebenzisi weqokobhe (uphononongo kunye nengxelo yevidiyo evela KubeCon EU'2020)

Ngenxa yoko, le checksum iya kubhaliswa kuzo zonke iipod, kwaye iya kufana naleyo yokusasazwa. Ngoku kufuneka uhlaziye isichasiselo xa iConfigMap itshintsha. Kwaye i-shell-operator iza luncedo kule meko. Konke okufuneka ukwenze yiprogram ikhonkco eliza rhuma kwi ConfigMap kwaye uhlaziye itshekhi.

Ukuba umsebenzisi wenza utshintsho kwi ConfigMap, iqokobhe-umqhubi uya kubaqaphela kwaye abale kwakhona itshekhisum. Emva koko umlingo weKubernetes uya kudlala: iorchestrator iya kubulala i-pod, yenze entsha, ilinde ukuba ibe yinto. Ready, kwaye udlulele kwelandelayo. Ngenxa yoko, ukusasazwa kuya kulungelelanisa kwaye kutshintshele kuguqulelo olutsha lweConfigMap.

Hamba? Bash! Dibana nomsebenzisi weqokobhe (uphononongo kunye nengxelo yevidiyo evela KubeCon EU'2020)

Umzekelo 2: Ukusebenza ngeeNkcazo zeZibonelelo eziSiko

Njengoko uyazi, i-Kubernetes ikuvumela ukuba wenze iintlobo zezinto eziqhelekileyo. Ngokomzekelo, unokwenza ububele MysqlDatabase. Masithi olu hlobo luneeparamitha ezimbini zemethadatha: name ΠΈ namespace.

apiVersion: example.com/v1alpha1
kind: MysqlDatabase
metadata:
  name: foo
  namespace: bar

Sineqela le-Kubernetes elineendawo ezahlukeneyo zamagama apho sinokwenza khona i-MySQL yolwazi. Kule meko i-shell-operator ingasetyenziselwa ukulandelela izixhobo MysqlDatabase, ezidibanisa kumncedisi we MySQL kwaye ungqamanisa iindawo ezifunwayo nezijongiweyo zeqela.

Hamba? Bash! Dibana nomsebenzisi weqokobhe (uphononongo kunye nengxelo yevidiyo evela KubeCon EU'2020)

Umzekelo 3: Ukubeka iliso kuNxibelelwano lweCluster

Njengoko usazi, ukusebenzisa i-ping yeyona ndlela ilula yokubeka iliso kwinethiwekhi. Kulo mzekelo siza kubonisa indlela yokuphumeza ukubeka iliso okunjalo usebenzisa i-shell-operator.

Okokuqala, kuya kufuneka ubhalisele ii-nodes. Umsebenzisi weqokobhe ufuna igama kunye nedilesi ye-IP yendawo nganye. Ngoncedo lwabo, uya kubetha ezi ndawo.

configVersion: v1
kubernetes:
- name: nodes
  apiVersion: v1
  kind: Node
  jqFilter: |
    {
      name: .metadata.name,
      ip: (
       .status.addresses[] |  
        select(.type == "InternalIP") |
        .address
      )
    }
  group: main
  keepFullObjectsInMemory: false
  executeHookOnEvent: []
schedule:
- name: every_minute
  group: main
  crontab: "* * * * *"

IParamu executeHookOnEvent: [] ivimbela i-hook ukuba iqhube ekuphenduleni nayiphi na isiganeko (oko kukuthi, ekuphenduleni ukutshintsha, ukongeza, ukucima ii-nodes). Nangona kunjalo, yena iya kubaleka (kwaye uhlaziye uluhlu lweendawo) Icwangcisiwe - ngomzuzu ngamnye, njengoko kumiselwe yintsimi schedule.

Ngoku umbuzo uvela, sazi njani malunga neengxaki ezifana nokulahleka kwepakethi? Makhe sijonge ikhowudi:

function __main__() {
  for i in $(seq 0 "$(context::jq -r '(.snapshots.nodes | length) - 1')"); do
    node_name="$(context::jq -r '.snapshots.nodes['"$i"'].filterResult.name')"
    node_ip="$(context::jq -r '.snapshots.nodes['"$i"'].filterResult.ip')"
    packets_lost=0
    if ! ping -c 1 "$node_ip" -t 1 ; then
      packets_lost=1
    fi
    cat >> "$METRICS_PATH" <<END
      {
        "name": "node_packets_lost",
        "add": $packets_lost,
        "labels": {
          "node": "$node_name"
        }
      }
END
  done
}

Siphindaphinda ngoluhlu lweenodi, sifumane amagama abo kunye needilesi ze-IP, sizifake kwaye sithumele iziphumo kwi-Prometheus. I-Shell-operator inokuthumela i-metrics kwi-Prometheus, igcina kwifayile ebekwe ngokomendo oxeliweyo kuguqulo lwemeko-bume $METRICS_PATH.

Nantsi njalo ungenza umsebenzi wokubeka iliso kwinethiwekhi kwiqela.

Indlela yokufola

Eli nqaku liza kuba lingagqibekanga ngaphandle kokuchaza enye indlela ebalulekileyo eyakhelwe kwi-shell-operator. Khawucinge ukuba iphumeza uhlobo oluthile lwehuku ekuphenduleni isiganeko kwiqela.

  • Kwenzeka ntoni ukuba, kwangaxeshanye, kukho into eyenzekayo kwiqela? enye kwakhon isiganeko?
  • Ngaba i-shell-operator iya kuqhuba omnye umzekelo wehuku?
  • Kuthekani ukuba, masithi, iziganeko ezihlanu zenzeke kwiqela ngexesha elinye?
  • Ngaba i-shell-operator iya kuziqhuba ngokuhambelanayo?
  • Kuthekani ngezixhobo ezisetyenzisiweyo ezifana nememori kunye ne-CPU?

Ngethamsanqa, i-shell-operator inesixhobo esakhelwe ngaphakathi sokufola. Yonke imisitho ibekwe emgceni kwaye iqhutywe ngokulandelelana.

Masiyizekelise oku ngemizekelo. Masithi sineegwegwe ezimbini. Isiganeko sokuqala siya kwikhonkco lokuqala. Nje ukuba inkqubo yayo igqityiwe, umgca uya phambili. Iziganeko ezintathu ezilandelayo ziqondiswe kwikhonkco yesibini - ziyasuswa emgceni kwaye zifakwe kuyo "kwi-bundle". Yiyo i hook ifumana uluhlu lweziganeko - okanye, ngokuchanekileyo, uluhlu lweemeko ezibophelelayo.

Kwakhona ezi iziganeko zinokudityaniswa zibe nkulu. Ipharamitha inoxanduva loku group kuqwalaselo olubophelelayo.

Hamba? Bash! Dibana nomsebenzisi weqokobhe (uphononongo kunye nengxelo yevidiyo evela KubeCon EU'2020)

Unokwenza naliphi na inani lemigca / iigwegwe kunye neendibaniselwano zabo ezahlukeneyo. Umzekelo, umgca omnye unokusebenza ngamagwegwe amabini, okanye ngokuphambeneyo.

Hamba? Bash! Dibana nomsebenzisi weqokobhe (uphononongo kunye nengxelo yevidiyo evela KubeCon EU'2020)

Konke okufuneka ukwenze kukuqwalasela intsimi ngokufanelekileyo queue kuqwalaselo olubophelelayo. Ukuba igama lomgca alichazwanga, ihuku ibaleka kumgca omiselweyo (default). Le ndlela yokufola ikuvumela ukuba uzisombulule ngokupheleleyo zonke iingxaki zolawulo lwemithombo xa usebenza ngamagwegwe.

isiphelo

Sachaza ukuba yintoni i-shell-operator, yabonisa indlela enokusetyenziswa ngayo ngokukhawuleza nangokukhawuleza ukudala abaqhubi be-Kubernetes, kwaye sinike imizekelo emininzi yokusetyenziswa kwayo.

Ulwazi oluneenkcukacha malunga neqokobhe-opharetha, kunye nesifundo esikhawulezayo malunga nendlela yokuyisebenzisa, iyafumaneka ngokuhambelanayo. iindawo zokugcina kwiGitHub. Ungalibazisi ukunxibelelana nathi ngemibuzo: unokuxoxa ngazo ngokukhethekileyo Iqela leTelegram (ngesiRashiya) okanye ngaphakathi le forum (ngesiNgesi).

Kwaye ukuba uyithandile, sihlala sonwabile ukubona imiba emitsha / PR / iinkwenkwezi kwiGitHub, apho, ngendlela, unokufumana abanye iiprojekthi ezinomdla. Phakathi kwabo kufanelekile ukugqamisa i-addon-operator, umkhuluwa we-shell-operator. Esi sixhobo sisebenzisa iitshathi zeHelm ukufaka i-add-ons, inokuhambisa ukuhlaziywa kunye nokubeka iliso kwiiparamitha / amaxabiso eetshathi ezahlukeneyo, ilawula inkqubo yokufakwa kweetshathi, kwaye inokuziguqula kwakhona ekuphenduleni iziganeko kwiqela.

Hamba? Bash! Dibana nomsebenzisi weqokobhe (uphononongo kunye nengxelo yevidiyo evela KubeCon EU'2020)

Iividiyo kunye nezilayidi

Ividiyo esuka kumdlalo (~23 imizuzu):


Ukunikezelwa kwengxelo:

PS

Funda nakwibhlog yethu:

umthombo: www.habr.com

Yongeza izimvo