ටැරන්ටූල් කාට්රිජ් මත පහසුවෙන් සහ ස්වභාවිකව යෙදුම් යෙදවීම (1 කොටස)

ටැරන්ටූල් කාට්රිජ් මත පහසුවෙන් සහ ස්වභාවිකව යෙදුම් යෙදවීම (1 කොටස)

අපි දැනටමත් කතා කර ඇත ටැරන්ටූල් කාට්රිජ්, බෙදා හරින ලද යෙදුම් සංවර්ධනය කිරීමට සහ ඒවා ඇසුරුම් කිරීමට ඔබට ඉඩ සලසයි. ඉතිරිව ඇත්තේ මෙම යෙදුම් යෙදවීමට සහ ඒවා කළමනාකරණය කිරීමට ඉගෙන ගැනීමයි. කණගාටු නොවන්න, අපි සියල්ල ආවරණය කර ඇත! අපි ටැරන්ටූල් කාට්රිජ් සමඟ වැඩ කිරීම සඳහා හොඳම භාවිතයන් සියල්ල එකතු කර ලිව්වෙමු ansible-role, එමඟින් පැකේජය සේවාදායකයන් වෙත බෙදා හැරීම, අවස්ථා දියත් කිරීම, ඒවා පොකුරකට ඒකාබද්ධ කිරීම, අවසරය වින්‍යාස කිරීම, bootstrap vshard, ස්වයංක්‍රීය අසාර්ථකත්වය සක්‍රීය කිරීම සහ පොකුරු වින්‍යාසය පැච් කිරීම සිදු කරයි.

රසවත්ද? එවිට කරුණාකර, කප්පාදුව යටතේ, අපි ඔබට පවසන අතර ඔබට සියල්ල පෙන්වන්නෙමු.

අපි උදාහරණයක් සමඟ ආරම්භ කරමු

අපි අපේ භූමිකාවේ ක්රියාකාරිත්වයේ කොටසක් පමණක් බලමු. ඔබට එහි සියලු හැකියාවන් සහ ආදාන පරාමිතීන් පිළිබඳ සම්පූර්ණ විස්තරයක් සැමවිටම සොයාගත හැකිය ලියකියවිලි. නමුත් එය සිය වතාවක් බැලීමට වඩා වරක් උත්සාහ කිරීම වඩා හොඳය, එබැවින් අපි කුඩා යෙදුමක් යොදමු.

ටැරන්ටූල් කාට්රිජ් ඇත නිබන්ධනය බැංකු සේවාලාභීන් සහ ඔවුන්ගේ ගිණුම් පිළිබඳ තොරතුරු ගබඩා කරන කුඩා කාට්රිජ් යෙදුමක් නිර්මාණය කිරීමට සහ HTTP හරහා දත්ත කළමනාකරණය සඳහා API ද සපයයි. මෙය සාක්ෂාත් කර ගැනීම සඳහා, උපග්රන්ථය හැකි භූමිකාවන් දෙකක් විස්තර කරයි: api и storage, අවස්ථා සඳහා පැවරිය හැකි.

ක්‍රියාවලි දියත් කරන ආකාරය ගැන කාට්‍රිජ් කිසිවක් නොකියයි, එය දැනටමත් ක්‍රියාත්මක වන අවස්ථා වින්‍යාස කිරීමේ හැකියාව පමණක් සපයයි. ඉතිරිය පරිශීලකයා විසින්ම කළ යුතුය: වින්‍යාස ගොනු සැකසීම, සේවා ආරම්භ කිරීම සහ ස්ථලකය වින්‍යාස කිරීම. නමුත් අපි මේ සියල්ල නොකරමු; ඇන්සිබල් අප වෙනුවෙන් එය කරනු ඇත.

වචන වලින් ක්‍රියාවට

එබැවින්, අපි අපගේ යෙදුම අතථ්‍ය යන්ත්‍ර දෙකකට යොදවා සරල ස්ථලකයක් සකසමු:

  • අනුරූ කට්ටලය app-1 භූමිකාව ක්රියාත්මක කරනු ඇත api, භූමිකාව ඇතුළත් වේ vshard-router. මෙහි එක් අවස්ථාවක් පමණක් වනු ඇත.
  • අනුරූ කට්ටලය storage-1 භූමිකාව ක්රියාත්මක කරයි storage (සහ ඒ සමගම vshard-storage), මෙහිදී අපි විවිධ යන්ත්‍ර වලින් අවස්ථා දෙකක් එකතු කරමු.

ටැරන්ටූල් කාට්රිජ් මත පහසුවෙන් සහ ස්වභාවිකව යෙදුම් යෙදවීම (1 කොටස)

අපට අවශ්ය ආදර්ශය ක්රියාත්මක කිරීමට වැග්රන්ට් и පිළිතුරු (අනුවාදය 2.8 හෝ ඊට වැඩි).

භූමිකාවම ඇත Ansible Galaxy. මෙය ඔබේ වැඩ බෙදා ගැනීමට සහ සූදානම් කළ භූමිකාවන් භාවිතා කිරීමට ඉඩ සලසන ගබඩාවකි.

අපි උදාහරණයකින් ගබඩාව ක්ලෝන කරමු:

$ git clone https://github.com/dokshina/deploy-tarantool-cartridge-app.git
$ cd deploy-tarantool-cartridge-app && git checkout 1.0.0

අපි අතථ්‍ය යන්ත්‍ර ඔසවන්නෙමු:

$ vagrant up

Tarantool Cartridge ansible භූමිකාව ස්ථාපනය කරන්න:

$ ansible-galaxy install tarantool.cartridge,1.0.1

ස්ථාපිත භූමිකාව දියත් කරන්න:

$ ansible-playbook -i hosts.yml playbook.yml

ක්‍රීඩා පොත ක්‍රියාත්මක කිරීම සම්පූර්ණ වන තෙක් අපි බලා සිටිමු, යන්න http://localhost:8181/admin/cluster/dashboard සහ ප්රතිඵලය භුක්ති විඳින්න:

ටැරන්ටූල් කාට්රිජ් මත පහසුවෙන් සහ ස්වභාවිකව යෙදුම් යෙදවීම (1 කොටස)

ඔබට දත්ත උඩුගත කළ හැකිය. නියමයි නේද?

දැන් අපි මෙය සමඟ වැඩ කරන්නේ කෙසේදැයි සොයා බලමු, ඒ සමඟම ස්ථලකයට තවත් අනුරුවක් එකතු කරන්න.

අපි එය තේරුම් ගැනීමට පටන් ගනිමු

ඉතින්, මොකද වුණේ?

අපි අතථ්‍ය යන්ත්‍ර දෙකක් පිහිටුවා අපගේ පොකුර වින්‍යාස කළ ඇන්සිබල් ක්‍රීඩා පොතක් දියත් කළෙමු. ගොනුවේ අන්තර්ගතය දෙස බලමු playbook.yml:

---
- name: Deploy my Tarantool Cartridge app
  hosts: all
  become: true
  become_user: root
  tasks:
  - name: Import Tarantool Cartridge role
    import_role:
      name: tarantool.cartridge

මෙහි රසවත් කිසිවක් සිදු නොවේ, අපි ඇන්සිබල් භූමිකාවක් දියත් කරමු tarantool.cartridge.

සියලුම වැදගත් දේවල් (එනම්, පොකුරු වින්‍යාසය) පිහිටා ඇත බඩු තොග-ගොනුව hosts.yml:

---
all:
  vars:
    # common cluster variables
    cartridge_app_name: getting-started-app
    cartridge_package_path: ./getting-started-app-1.0.0-0.rpm  # path to package

    cartridge_cluster_cookie: app-default-cookie  # cluster cookie

    # common ssh options
    ansible_ssh_private_key_file: ~/.vagrant.d/insecure_private_key
    ansible_ssh_common_args: '-o IdentitiesOnly=yes -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'

  # INSTANCES
  hosts:
    storage-1:
      config:
        advertise_uri: '172.19.0.2:3301'
        http_port: 8181

    app-1:
      config:
        advertise_uri: '172.19.0.3:3301'
        http_port: 8182

    storage-1-replica:
      config:
        advertise_uri: '172.19.0.3:3302'
        http_port: 8183

  children:
    # GROUP INSTANCES BY MACHINES
    host1:
      vars:
        # first machine connection options
        ansible_host: 172.19.0.2
        ansible_user: vagrant

      hosts:  # instances to be started on the first machine
        storage-1:

    host2:
      vars:
        # second machine connection options
        ansible_host: 172.19.0.3
        ansible_user: vagrant

      hosts:  # instances to be started on the second machine
        app-1:
        storage-1-replica:

    # GROUP INSTANCES BY REPLICA SETS
    replicaset_app_1:
      vars:  # replica set configuration
        replicaset_alias: app-1
        failover_priority:
          - app-1  # leader
        roles:
          - 'api'

      hosts:  # replica set instances
        app-1:

    replicaset_storage_1:
      vars:  # replica set configuration
        replicaset_alias: storage-1
        weight: 3
        failover_priority:
          - storage-1  # leader
          - storage-1-replica
        roles:
          - 'storage'

      hosts:   # replica set instances
        storage-1:
        storage-1-replica:

අපට අවශ්‍ය වන්නේ මෙම ගොනුවේ අන්තර්ගතය වෙනස් කිරීමෙන් අවස්ථා සහ අනුරූ කළමනාකරණය කරන්නේ කෙසේදැයි ඉගෙන ගැනීමයි. මීළඟට අපි එයට නව කොටස් එකතු කරමු. ඒවා එකතු කළ යුත්තේ කොතැනද යන්න ව්‍යාකූල නොවීමට, ඔබට මෙම ගොනුවේ අවසාන අනුවාදය දෙස බැලිය හැකිය, hosts.updated.yml, උදාහරණ ගබඩාවේ ඇති.

අවස්ථා කළමනාකරණය

Ansible පද වලින්, සෑම අවස්ථාවක්ම ධාරකයකි (දෘඪාංග සේවාදායකයක් සමඟ පටලවා නොගත යුතුය), i.e. Ansible කළමනාකරණය කරනු ලබන යටිතල පහසුකම් නෝඩය. එක් එක් ධාරකය සඳහා අපට සම්බන්ධතා පරාමිතීන් නියම කළ හැක (උදා ansible_host и ansible_user), මෙන්ම නිදසුන් වින්‍යාසය. අවස්ථා විස්තරය කොටසේ ඇත hosts.

අපි උදාහරණ වින්‍යාසය දෙස බලමු storage-1:

all:
  vars:
    ...

  # INSTANCES
  hosts:
    storage-1:
      config:
        advertise_uri: '172.19.0.2:3301'
        http_port: 8181

  ...

විචල්‍යයක config අපි නිදසුන් පරාමිති නියම කළෙමු - advertise URI и HTTP port.
පහත දැක්වෙන්නේ නිදසුන් පරාමිතීන් ය app-1 и storage-1-replica.

අපි ඇන්සිබල්ට එක් එක් අවස්ථාව සඳහා සම්බන්ධතා පරාමිතීන් පැවසිය යුතුයි. අවස්ථා අථත්‍ය යන්ත්‍ර කණ්ඩායම් ලෙස කාණ්ඩ කිරීම තාර්කික බව පෙනේ. මෙම අරමුණු සඳහා, අවස්ථා කණ්ඩායම් වශයෙන් ඒකාබද්ධ වේ host1 и host2, සහ කොටසේ එක් එක් කණ්ඩායම තුළ vars අගයන් දක්වා ඇත ansible_host и ansible_user එක් අතථ්‍ය යන්ත්‍රයක් සඳහා. සහ කොටසේ hosts — මෙම කණ්ඩායමට ඇතුළත් ධාරක (එනම් අවස්ථා):

all:
  vars:
    ...
  hosts:
    ...
  children:
    # GROUP INSTANCES BY MACHINES
    host1:
      vars:
        # first machine connection options
        ansible_host: 172.19.0.2
        ansible_user: vagrant
       hosts:  # instances to be started on the first machine
        storage-1:

     host2:
      vars:
        # second machine connection options
        ansible_host: 172.19.0.3
        ansible_user: vagrant
       hosts:  # instances to be started on the second machine
        app-1:
        storage-1-replica:

අපි වෙනස් වීමට පටන් ගනිමු hosts.yml. අපි තවත් අවස්ථා දෙකක් එකතු කරමු, storage-2-replica පළමු අතථ්‍ය යන්ත්‍රය මත සහ storage-2 දෙවනුව:

all:
  vars:
    ...

  # INSTANCES
  hosts:
    ...
    storage-2:  # <==
      config:
        advertise_uri: '172.19.0.3:3303'
        http_port: 8184

    storage-2-replica:  # <==
      config:
        advertise_uri: '172.19.0.2:3302'
        http_port: 8185

  children:
    # GROUP INSTANCES BY MACHINES
    host1:
      vars:
        ...
      hosts:  # instances to be started on the first machine
        storage-1:
        storage-2-replica:  # <==

    host2:
      vars:
        ...
      hosts:  # instances to be started on the second machine
        app-1:
        storage-1-replica:
        storage-2:  # <==
  ...

ඇසිබල් ක්‍රීඩා පොත දියත් කරන්න:

$ ansible-playbook -i hosts.yml 
                   --limit storage-2,storage-2-replica 
                   playbook.yml

කරුණාකර විකල්පය සටහන් කරන්න --limit. සෑම පොකුරු අවස්ථාවක්ම Ansible පද වලින් ධාරකයක් වන බැවින්, playbook ක්‍රියාත්මක කිරීමේදී කුමන අවස්ථා වින්‍යාස කළ යුතුද යන්න අපට පැහැදිලිව සඳහන් කළ හැක.

වෙබ් UI වෙත ආපසු යාම http://localhost:8181/admin/cluster/dashboard සහ අපගේ නව අවස්ථා බලන්න:

ටැරන්ටූල් කාට්රිජ් මත පහසුවෙන් සහ ස්වභාවිකව යෙදුම් යෙදවීම (1 කොටස)

එතැනින් නොනැවතී ස්ථල විද්‍යාව කළමනාකරණය ප්‍රගුණ කරමු.

ස්ථල විද්‍යාව කළමනාකරණය

අපි අපගේ නව අවස්ථා අනුරූ කට්ටලයකට ඒකාබද්ධ කරමු storage-2. අපි අලුත් කණ්ඩායමක් එකතු කරමු replicaset_storage_2 සහ එහි විචල්‍යවල අනුරූ පරාමිති සමඟ ප්‍රතිසමයෙන් විස්තර කරන්න replicaset_storage_1. කොටසේ hosts මෙම කණ්ඩායමට ඇතුළත් වන්නේ කුමන අවස්ථාද යන්න සඳහන් කරමු (එනම්, අපගේ අනුරූ කට්ටලය):

---
all:
  vars:
    ...
  hosts:
    ...
  children:
    ...
    # GROUP INSTANCES BY REPLICA SETS
    ...
    replicaset_storage_2:  # <==
      vars:  # replicaset configuration
        replicaset_alias: storage-2
        weight: 2
        failover_priority:
          - storage-2
          - storage-2-replica
        roles:
          - 'storage'

      hosts:   # replicaset instances
        storage-2:
        storage-2-replica:

අපි නැවත ක්‍රීඩා පොත ආරම්භ කරමු:

$ ansible-playbook -i hosts.yml 
                   --limit replicaset_storage_2 
                   --tags cartridge-replicasets 
                   playbook.yml

පරාමිතිය තුළ --limit මෙවර අපි අපගේ අනුපිටපතට අනුරූප වන කණ්ඩායමේ නම සම්මත කළෙමු.

විකල්පය සලකා බලමු tags.

අපගේ භූමිකාව අනුක්‍රමිකව විවිධ කාර්යයන් ඉටු කරයි, ඒවා පහත ටැග් වලින් සලකුණු කර ඇත:

  • cartridge-instances: නිදසුන් කළමනාකරණය (වින්‍යාස කිරීම, සාමාජිකත්වයට සම්බන්ධ වීම);
  • cartridge-replicasets: ස්ථල විද්‍යාව කළමනාකරණය (ප්‍රතිනිර්මාණය කළමණාකරණය සහ පොකුරෙන් අවස්ථා ස්ථිරව ඉවත් කිරීම (පහර දැමීම);
  • cartridge-config: අනෙකුත් පොකුරු පරාමිතීන් කළමනාකරණය (vshard bootstrapping, ස්වයංක්‍රීය අසාර්ථක මාදිලිය, අවසර පරාමිති සහ යෙදුම් වින්‍යාසය).

අපට කිරීමට අවශ්‍ය කාර්යයේ කුමන කොටසද යන්න අපට පැහැදිලිව සඳහන් කළ හැකිය, එවිට කාර්යභාරය ඉතිරි කාර්යයන් මඟ හරිනු ඇත. අපගේ නඩුවේදී, අපට වැඩ කිරීමට අවශ්‍ය වන්නේ ස්ථලකය සමඟ පමණි, එබැවින් අපි නියම කළෙමු cartridge-replicasets.

අපගේ උත්සාහයේ ප්‍රතිඵලය අපි තක්සේරු කරමු. අපි නව අනුපිටපතක් සොයා ගනිමු http://localhost:8181/admin/cluster/dashboard.

ටැරන්ටූල් කාට්රිජ් මත පහසුවෙන් සහ ස්වභාවිකව යෙදුම් යෙදවීම (1 කොටස)

හුරේ!

අවස්ථා සහ අනුරූ කට්ටලවල වින්‍යාසය වෙනස් කිරීම සමඟ අත්හදා බැලීම සහ පොකුරු ස්ථලකය වෙනස් වන ආකාරය බලන්න. ඔබට විවිධ මෙහෙයුම් අවස්ථා උත්සාහ කළ හැකිය, උදා. rolling update හෝ වැඩි කිරීම memtx_memory. ඔබගේ යෙදුමේ ඇති විය හැකි අක්‍රිය කාලය අඩු කිරීම සඳහා භූමිකාව නැවත ආරම්භ නොකර මෙය කිරීමට උත්සාහ කරනු ඇත.

දුවන්න අමතක කරන්න එපා vagrant haltඅතථ්‍ය යන්ත්‍ර සමඟ වැඩ කර අවසන් වූ පසු ඒවා නැවැත්වීමට.

තොප්පිය යට ඇත්තේ කුමක්ද?

අපගේ අත්හදා බැලීම් වලදී ඇසිබල් භූමිකාව යටතේ සිදුවෙමින් පවතින දේ ගැන මම ඔබට වැඩි විස්තර කියන්නම්.

අපි බලමු Cartridge යෙදුම පියවරෙන් පියවර යෙදවීම.

පැකේජය ස්ථාපනය කිරීම සහ ආරම්භක අවස්ථා

පළමුව ඔබ පැකේජය සේවාදායකයට ලබා දී එය ස්ථාපනය කළ යුතුය. දැනට භූමිකාව RPM සහ DEB පැකේජ සමඟ වැඩ කළ හැක.

ඊළඟට අපි අවස්ථා දියත් කරමු. මෙහි සෑම දෙයක්ම ඉතා සරලයි: සෑම අවස්ථාවක්ම වෙනම වේ systemd-සේවය. මම ඔබට උදාහරණයක් දෙන්නම්:

$ systemctl start myapp@storage-1

මෙම විධානය එම අවස්ථාව දියත් කරනු ඇත storage-1 යෙදුම් myapp. දියත් කළ අවස්ථාව එහි සොයනු ඇත වින්යාසය в /etc/tarantool/conf.d/. නිදසුන් ලඝු-සටහන් භාවිතා කර බැලිය හැක journald.

ඒකක ගොනුව /etc/systemd/system/[email protected] systemd සේවාව සඳහා පැකේජය සමඟ බෙදා හරිනු ලැබේ.

ඇන්සිබල් සතුව පැකේජ ස්ථාපනය කිරීම සහ systemd සේවා කළමනාකරණය කිරීම සඳහා බිල්ට් මොඩියුල ඇත; අපි මෙහි අලුත් කිසිවක් නිර්මාණය කර නැත.

පොකුරු ස්ථලකය සැකසීම

විනෝදය ආරම්භ වන්නේ මෙතැනිනි. එකඟ වන්න, පැකේජ ස්ථාපනය කිරීම සහ ධාවනය කිරීම සඳහා විශේෂ Ansible භූමිකාවක් සමඟ කරදර වීම අමුතු දෙයක් වනු ඇත systemd-සේවා.

ඔබට පොකුර අතින් සැකසිය හැක:

  • පළමු විකල්පය: Web UI විවෘත කර බොත්තම් මත ක්ලික් කරන්න. අවස්ථා කිහිපයක එක් වරක් ආරම්භයක් සඳහා එය බෙහෙවින් සුදුසු ය.
  • දෙවන විකල්පය: ඔබට GraphQl API භාවිතා කළ හැක. මෙන්න ඔබට දැනටමත් යමක් ස්වයංක්‍රීය කළ හැකිය, උදාහරණයක් ලෙස, පයිතන් හි ස්ක්‍රිප්ට් එකක් ලියන්න.
  • තුන්වන විකල්පය (ශක්තිමත් කැමැත්ත සඳහා): සේවාදායකය වෙත ගොස්, භාවිතා කරන එක් අවස්ථාවක් වෙත සම්බන්ධ වන්න tarantoolctl connect සහ Lua මොඩියුලය සමඟ අවශ්ය සියලු උපාමාරු සිදු කරන්න cartridge.

අපගේ නව නිපැයුමේ ප්රධාන කාර්යය වන්නේ මෙය හරියටම සිදු කිරීමයි, ඔබ සඳහා කාර්යයේ වඩාත්ම දුෂ්කර කොටසයි.

ඇන්සිබල් ඔබට ඔබේම මොඩියුලයක් ලිවීමට සහ එය භූමිකාවක් ලෙස භාවිතා කිරීමට ඉඩ සලසයි. අපගේ කාර්යභාරය විවිධ පොකුරු සංරචක කළමනාකරණය කිරීමට එවැනි මොඩියුල භාවිතා කරයි.

එය ක්රියා කරන්නේ කෙසේද? ඔබ ප්‍රකාශන වින්‍යාසය තුළ පොකුරේ අපේක්ෂිත තත්ත්වය විස්තර කරන අතර, භූමිකාව මඟින් එක් එක් මොඩියුලයට එහි වින්‍යාස අංශය ආදානය ලෙස සපයයි. මොඩියුලය පොකුරේ වත්මන් තත්වය ලබා ගන්නා අතර එය ආදානය ලෙස ලැබුණු දේ සමඟ සංසන්දනය කරයි. ඊළඟට, එක් අවස්ථාවක සොකට් එක හරහා කේතයක් දියත් කරනු ලැබේ, එමඟින් පොකුර අපේක්ෂිත තත්වයට ගෙන එයි.

ප්රතිඵල

අද අපි කිව්වා සහ පෙන්නුවා ඔබේ යෙදුම Tarantool Cartridge වෙත යොදවා සරල ස්ථලකයක් සකසන ආකාරය. මෙය සිදු කිරීම සඳහා, අපි භාවිතා කළ Ansible - භාවිතා කිරීමට පහසු වන බලවත් මෙවලමක් වන අතර බොහෝ යටිතල පහසුකම් නෝඩ් එකවර වින්‍යාස කිරීමට ඔබට ඉඩ සලසයි (අපගේ නඩුවේදී, පොකුරු අවස්ථා).

ඉහත අපි ඇන්සිබල් භාවිතයෙන් පොකුරු වින්‍යාසයක් විස්තර කිරීමට බොහෝ ක්‍රමවලින් එකක් දෙස බැලුවෙමු. ඔබ ඉදිරියට යාමට සූදානම් යැයි හැඟුණු පසු, ගවේෂණය කරන්න හොඳ පරිචයන් සෙල්ලම් පොත් ලිවීම ගැන. ඔබගේ ස්ථල විද්‍යාව භාවිතයෙන් කළමනාකරණය කිරීම ඔබට පහසු විය හැක group_vars и host_vars.

ඉතා ඉක්මනින් අපි ඔබට ස්ථල විද්‍යාවෙන් නිදසුන් ස්ථිරව මකා දමන්නේ (පහර දැමීම) කරන්නේ කෙසේද, vshard bootstrap, ස්වයංක්‍රීය අසාර්ථක ප්‍රකාරය කළමනාකරණය කිරීම, අවසරය වින්‍යාස කිරීම සහ පොකුරු වින්‍යාසය පැච් කරන්නේ කෙසේද යන්න ඔබට කියන්නෙමු. මේ අතර, ඔබට තනිවම ඉගෙන ගත හැකිය ලේඛනගත කිරීම සහ පොකුරු පරාමිතීන් වෙනස් කිරීම සමඟ අත්හදා බැලීම.

යමක් වැඩ නොකරන්නේ නම්, එය කිරීමට වග බලා ගන්න මට දැනගන්න සලස්වන්න ගැටලුව ගැන අපට. අපි සියල්ල ඉක්මනින් විසඳන්නෙමු!

මූලාශ්රය: www.habr.com

අදහස් එක් කරන්න