Docker Swarm සමඟ යෙදුම් යෙදවීම

අප වැඩ කරමින් සිටින සබැඳි වීඩියෝ අන්තර්ගත නිර්දේශ පද්ධතිය සංවෘත වාණිජ සංවර්ධනයක් වන අතර එය තාක්ෂණික වශයෙන් හිමිකාර සහ විවෘත මූලාශ්‍ර සංරචකවල බහු සංරචක සමූහයකි. මෙම ලිපිය ලිවීමේ පරමාර්ථය වන්නේ සීමිත කාලයක් තුළ අපගේ ක්‍රියාවලීන්හි ස්ථාපිත කාර්ය ප්‍රවාහයට බාධා නොකර වේදිකාගත අඩවියක් සඳහා ඩොකර් රංචු පොකුරු පද්ධතිය ක්‍රියාත්මක කිරීම විස්තර කිරීමයි. ඔබේ අවධානයට ඉදිරිපත් කරන ලද ආඛ්‍යානය කොටස් දෙකකට බෙදා ඇත. පළමු කොටස docker swarm භාවිතා කිරීමට පෙර CI / CD විස්තර කරන අතර, දෙවන කොටස එය ක්රියාත්මක කිරීමේ ක්රියාවලිය විස්තර කරයි. පළමු කොටස කියවීමට උනන්දුවක් නොදක්වන අයට ආරක්ෂිතව දෙවන කොටස වෙත යා හැකිය.

XNUMX වන කොටස

ඈත, ඈත අවුරුද්දේ, හැකි ඉක්මනින් CI / CD ක්‍රියාවලිය සැකසීමට අවශ්‍ය විය. එක් කොන්දේසියක් වූයේ ඩොකර් භාවිතා නොකිරීමයි යෙදවීම සඳහා හේතු කිහිපයක් නිසා සංවර්ධිත සංරචක:

  • නිෂ්පාදනයේ සංරචකවල වඩාත් විශ්වාසදායක සහ ස්ථාවර ක්‍රියාකාරිත්වය සඳහා (එනම්, ඇත්ත වශයෙන්ම, අථත්‍යකරණය භාවිතා නොකිරීමේ අවශ්‍යතාවය)
  • ප්‍රමුඛ සංවර්ධකයින්ට ඩොකර් සමඟ වැඩ කිරීමට අවශ්‍ය නොවීය (අමුතුයි, නමුත් එය එසේ විය)
  • පර්යේෂණ සහ සංවර්ධන කළමනාකරණයේ දෘෂ්ටිවාදාත්මක පදනම මත

MVP සඳහා යටිතල පහසුකම්, තොග සහ ආසන්න මූලික අවශ්‍යතා පහත පරිදි ඉදිරිපත් කරන ලදී:

  • Debian සමඟ Intel® X4 සේවාදායකයන් 5650 (තවත් එක් බලවත් යන්ත්‍රයක් සම්පූර්ණයෙන්ම සංවර්ධනය කර ඇත)
  • තමන්ගේම අභිරුචි සංරචක සංවර්ධනය C ++, Python3 හි සිදු කෙරේ
  • භාවිතා කරන ප්‍රධාන 3වන පාර්ශවීය මෙවලම්: Kafka, Clickhouse, Airflow, Redis, Grafana, Postgresql, Mysql, ...
  • නිදොස් කිරීම සහ මුදා හැරීම සඳහා වෙන වෙනම සංරචක ගොඩනැගීම සහ පරීක්ෂා කිරීම සඳහා නල මාර්ග

ආරම්භක අදියරේදී ආමන්ත්‍රණය කළ යුතු පළමු ප්‍රශ්නවලින් එකක් වන්නේ අභිරුචි සංරචක ඕනෑම පරිසරයක (CI / CD) යොදවන්නේ කෙසේද යන්නයි.

අපි තුන්වන පාර්ශ්වීය සංරචක ක්රමානුකූලව ස්ථාපනය කිරීමට සහ ඒවා ක්රමානුකූලව යාවත්කාලීන කිරීමට තීරණය කළා. C++ හෝ Python හි සංවර්ධනය කරන ලද අභිරුචි යෙදුම් ක්‍රම කිහිපයකින් යෙදවිය හැක. ඒවා අතර, උදාහරණයක් ලෙස: පද්ධති පැකේජ නිර්මාණය කිරීම, ගොඩනඟන ලද පින්තූර ගබඩාව වෙත යැවීම සහ ඒවා සේවාදායකයන් මත ස්ථාපනය කිරීම. නොදන්නා හේතුවක් නිසා, වෙනත් ක්‍රමයක් තෝරා ගන්නා ලදී, එනම්: CI භාවිතයෙන්, යෙදුම් ක්‍රියාත්මක කළ හැකි ගොනු සම්පාදනය කර ඇත, අතථ්‍ය ව්‍යාපෘති පරිසරයක් සාදනු ලැබේ, py මොඩියුල අවශ්‍යතා.txt වෙතින් ස්ථාපනය කර ඇත, සහ මෙම සියලු පුරාවස්තු වින්‍යාස, ස්ක්‍රිප්ට් සහ යවනු ලැබේ. සේවාදායක වෙත යෙදුම් පරිසරය සමඟ. ඊළඟට, යෙදුම් පරිපාලක අයිතිවාසිකම් නොමැතිව අතථ්‍ය පරිශීලකයෙකු ලෙස දියත් කෙරේ.

CI/CD පද්ධතිය ලෙස Gitlab-CI තෝරා ගන්නා ලදී. ප්රතිඵලයක් ලෙස නල මාර්ගය මේ වගේ දෙයක් විය:

Docker Swarm සමඟ යෙදුම් යෙදවීම
ව්‍යුහාත්මකව, gitlab-ci.yml මේ ආකාරයට දිස් විය

---
variables:
  # минимальная версия ЦПУ на серверах, где разворачивается кластер
  CMAKE_CPUTYPE: "westmere"

  DEBIAN: "MYREGISTRY:5000/debian:latest"

before_script:
  - eval $(ssh-agent -s)
  - ssh-add <(echo "$SSH_PRIVATE_KEY")
  - mkdir -p ~/.ssh && echo -e "Host *ntStrictHostKeyChecking nonn" > ~/.ssh/config

stages:
  - build
  - testing
  - deploy

debug.debian:
  stage: build
  image: $DEBIAN
  script:
    - cd builds/release && ./build.sh
    paths:
      - bin/
      - builds/release/bin/
    when: always
release.debian:
  stage: build
  image: $DEBIAN
  script:
    - cd builds/release && ./build.sh
    paths:
      - bin/
      - builds/release/bin/
    when: always

## testing stage
tests.codestyle:
  stage: testing
  image: $DEBIAN
  dependencies:
    - release.debian
  script:
    - /bin/bash run_tests.sh -t codestyle -b "${CI_COMMIT_REF_NAME}_codestyle"
tests.debug.debian:
  stage: testing
  image: $DEBIAN
  dependencies:
    - debug.debian
  script:
    - /bin/bash run_tests.sh -e codestyle/test_pylint.py -b "${CI_COMMIT_REF_NAME}_debian_debug"
  artifacts:
    paths:
      - run_tests/username/
    when: always
    expire_in: 1 week
tests.release.debian:
  stage: testing
  image: $DEBIAN
  dependencies:
    - release.debian
  script:
    - /bin/bash run_tests.sh -e codestyle/test_pylint.py -b "${CI_COMMIT_REF_NAME}_debian_release"
  artifacts:
    paths:
      - run_tests/username/
    when: always
    expire_in: 1 week

## staging stage
deploy_staging:
  stage: deploy
  environment: staging
  image: $DEBIAN
  dependencies:
    - release.debian
  script:
    - cd scripts/deploy/ &&
        python3 createconfig.py -s $CI_ENVIRONMENT_NAME &&
        /bin/bash install_venv.sh -d -r ../../requirements.txt &&
        python3 prepare_init.d.py &&
        python3 deploy.py -s $CI_ENVIRONMENT_NAME
  when: manual

එකලස් කිරීම සහ පරීක්ෂා කිරීම එහිම රූපය මත සිදු කරන බව සඳහන් කිරීම වටී, අවශ්ය සියලු පද්ධති පැකේජ දැනටමත් ස්ථාපනය කර ඇති අතර අනෙකුත් සැකසුම් සකස් කර ඇත.

රැකියා වල මෙම එක් එක් ස්ක්‍රිප්ට් එකක්ම තමන්ගේම ආකාරයෙන් සිත්ගන්නාසුළු වුවද, ඇත්ත වශයෙන්ම මම ඒවා ගැන කතා නොකරමි, ඒ සෑම එකක්ම විස්තර කිරීමට බොහෝ කාලයක් ගතවනු ඇති අතර මෙය ලිපියේ අරමුණ නොවේ. මම ඔබේ අවධානය යොමු කරන්නේ යෙදවීමේ අදියර ඇමතුම් ස්ක්‍රිප්ට් අනුපිළිවෙලකින් සමන්විත වන බවට පමණි:

  1. createconfig.py - පසුව යෙදවීම සඳහා විවිධ පරිසරවල සංරචක සැකසුම් සහිත settings.ini ගොනුවක් නිර්මාණය කරයි (පූර්වනිෂ්පාදනය, නිෂ්පාදනය, පරීක්ෂා කිරීම, ...)
  2. install_venv.sh - නිශ්චිත නාමාවලියක py සංරචක සඳහා අතථ්‍ය පරිසරයක් නිර්මාණය කර එය දුරස්ථ සේවාදායකයන් වෙත පිටපත් කරයි
  3. සූදානම්_init.d.py — සැකිල්ල මත පදනම්ව සංරචකය සඳහා ආරම්භක-නැවතුම් ස්ක්‍රිප්ට් සකස් කරයි
  4. deploy.py - නව සංරචක දිරාපත් කර නැවත ආරම්භ කරයි

කාලය ගෙවී ගියේය. වේදිකා අදියර පූර්ව නිෂ්පාදන සහ නිෂ්පාදනය මගින් ප්‍රතිස්ථාපනය විය. තවත් එක් බෙදාහැරීමක් (CentOS) මත නිෂ්පාදනය සඳහා සහය එක් කරන ලදී. තවත් බලවත් භෞතික සේවාදායකයන් 5ක් සහ අතථ්‍ය ඒවා දුසිමක් එකතු කරන ලදී. වැඩ කරන රාජ්‍යයට වැඩි හෝ අඩුවෙන් සමීප පරිසරයක ඔවුන්ගේ කාර්යයන් පරීක්ෂා කිරීම සංවර්ධකයින්ට සහ පරීක්ෂකයින්ට වඩ වඩාත් දුෂ්කර විය. මෙම අවස්ථාවේදී, ඔහු නොමැතිව කළ නොහැකි බව පැහැදිලි විය ...

II කොටස

Docker Swarm සමඟ යෙදුම් යෙදවීම

එබැවින්, අපගේ පොකුර යනු Dockerfiles විසින් විස්තර නොකරන ලද වෙනම සංරචක දුසිම් කිහිපයක දර්ශනීය පද්ධතියකි. ඔබට එය වින්‍යාසගත කළ හැක්කේ සාමාන්‍යයෙන් නිශ්චිත පරිසරයකට යෙදවීම සඳහා පමණි. අපගේ කර්තව්‍යය වන්නේ පොකුර පූර්ව නිකුතුව පරීක්ෂා කිරීමට පෙර එය පරීක්ෂා කිරීම සඳහා වේදිකා පරිසරයකට යෙදවීමයි.

න්‍යායාත්මකව, සමගාමීව ක්‍රියාත්මක වන පොකුරු කිහිපයක් තිබිය හැක: සම්පුර්ණ කරන ලද තත්වයේ හෝ නිම කිරීමට ආසන්න කාර්යයන් ප්‍රමාණයක් ඇත. අප සතුව ඇති සර්වර් වල ධාරිතාවය එක් එක් සේවාදායකයේ පොකුරු කිහිපයක් ධාවනය කිරීමට අපට ඉඩ සලසයි. සෑම වේදිකා පොකුරක්ම හුදකලා විය යුතුය (වරාය, නාමාවලි ආදියෙහි ඡේදනයක් නොතිබිය යුතුය).

අපගේ වටිනාම සම්පත අපගේ කාලය වන අතර අපට එය එතරම් නොතිබුණි.

වේගවත් ආරම්භයක් සඳහා, අපි එහි සරල බව සහ ගෘහ නිර්මාණ ශිල්පීය නම්‍යශීලීභාවය හේතුවෙන් Docker Swarm තෝරා ගත්තෙමු. අප කළ පළමු දෙය නම් දුරස්ථ සේවාදායකයන් මත කළමනාකරුවෙකු සහ නෝඩ් කිහිපයක් නිර්මාණය කිරීමයි:

$ docker node ls
ID                            HOSTNAME            STATUS              AVAILABILITY        MANAGER STATUS      ENGINE VERSION
kilqc94pi2upzvabttikrfr5d     nop-test-1     Ready               Active                                  19.03.2
jilwe56pl2zvabupryuosdj78     nop-test-2     Ready               Active                                  19.03.2
j5a4yz1kr2xke6b1ohoqlnbq5 *   nop-test-3     Ready               Active              Leader              19.03.2

ඊළඟට, ජාලයක් සාදන්න:


$ docker network create --driver overlay --subnet 10.10.10.0/24 nw_swarm

මීලඟට, අපි CI වෙතින් නෝඩ් වල දුරස්ථ පාලකය අනුව Gitlab-CI සහ Swarm නෝඩ් සම්බන්ධ කළෙමු: සහතික ස්ථාපනය කිරීම, රහස් විචල්‍යයන් සැකසීම සහ පාලන සේවාදායකයේ ඩොකර් සේවාව සැකසීම. මේක ලිපියක් අපිට ගොඩක් කාලය ඉතිරි කළා.

මීළඟට, අපි .gitlab-ci .yml වෙත අට්ටි නිර්මාණය සහ විනාශ කිරීමේ රැකියා එක් කළෙමු.

.gitlab-ci .yml වෙත තවත් රැකියා කිහිපයක් එකතු කර ඇත

## staging stage
deploy_staging:
  stage: testing
  before_script:
    - echo "override global 'before_script'"
  image: "REGISTRY:5000/docker:latest"
  environment: staging
  dependencies: []
  variables:
    DOCKER_CERT_PATH: "/certs"
    DOCKER_HOST: tcp://10.50.173.107:2376
    DOCKER_TLS_VERIFY: 1
    CI_BIN_DEPENDENCIES_JOB: "release.centos.7"
  script:
    - mkdir -p $DOCKER_CERT_PATH
    - echo "$TLSCACERT" > $DOCKER_CERT_PATH/ca.pem
    - echo "$TLSCERT" > $DOCKER_CERT_PATH/cert.pem
    - echo "$TLSKEY" > $DOCKER_CERT_PATH/key.pem
    - docker stack deploy -c docker-compose.yml ${CI_ENVIRONMENT_NAME}_${CI_COMMIT_REF_NAME} --with-registry-auth
    - rm -rf $DOCKER_CERT_PATH
  when: manual

## stop staging stage
stop_staging:
  stage: testing
  before_script:
    - echo "override global 'before_script'"
  image: "REGISTRY:5000/docker:latest"
  environment: staging
  dependencies: []
  variables:
    DOCKER_CERT_PATH: "/certs"
    DOCKER_HOST: tcp://10.50.173.107:2376
    DOCKER_TLS_VERIFY: 1
  script:
    - mkdir -p $DOCKER_CERT_PATH
    - echo "$TLSCACERT" > $DOCKER_CERT_PATH/ca.pem
    - echo "$TLSCERT" > $DOCKER_CERT_PATH/cert.pem
    - echo "$TLSKEY" > $DOCKER_CERT_PATH/key.pem
    - docker stack rm ${CI_ENVIRONMENT_NAME}_${CI_COMMIT_REF_NAME}
    # TODO: need check that stopped
  when: manual

ඉහත කේත කොටසින්, අතින් ක්‍රියා කිරීම අවශ්‍ය වන නල මාර්ග වෙත බොත්තම් දෙකක් (deploy_staging, stop_staging) එක් කර ඇති බව ඔබට දැක ගත හැක.

Docker Swarm සමඟ යෙදුම් යෙදවීම
තොගයේ නම ශාඛා නාමයට ගැළපෙන අතර මෙම සුවිශේෂත්වය ප්‍රමාණවත් විය යුතුය. තොගයේ ඇති සේවාවන්ට අද්විතීය ip ලිපින සහ වරාය, නාමාවලි ආදිය ලැබේ. හුදකලා වනු ඇත, නමුත් අට්ටියේ සිට තොගය දක්වා එකම වේ (වින්‍යාස ගොනුව සියලුම අට්ටි සඳහා සමාන බැවින්) - අපට අවශ්‍ය දේ. අපි භාවිතා කරමින් තොගය (පොකුරු) යොදවන්නෙමු docker-compose.yml, එය අපගේ පොකුර විස්තර කරයි.

docker-compose.yml

---
version: '3'

services:
  userprop:
    image: redis:alpine
    deploy:
      replicas: 1
      placement:
        constraints: [node.id == kilqc94pi2upzvabttikrfr5d]
      restart_policy:
        condition: none
    networks:
      nw_swarm:
  celery_bcd:
    image: redis:alpine
    deploy:
      replicas: 1
      placement:
        constraints: [node.id == kilqc94pi2upzvabttikrfr5d]
      restart_policy:
        condition: none
    networks:
      nw_swarm:

  schedulerdb:
    image: mariadb:latest
    environment:
      MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
      MYSQL_DATABASE: schedulerdb
      MYSQL_USER: ****
      MYSQL_PASSWORD: ****
    command: ['--character-set-server=utf8mb4', '--collation-server=utf8mb4_unicode_ci', '--explicit_defaults_for_timestamp=1']
    deploy:
      replicas: 1
      placement:
        constraints: [node.id == kilqc94pi2upzvabttikrfr5d]
      restart_policy:
        condition: none
    networks:
      nw_swarm:

  celerydb:
    image: mariadb:latest
    environment:
      MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
      MYSQL_DATABASE: celerydb
      MYSQL_USER: ****
      MYSQL_PASSWORD: ****
    deploy:
      replicas: 1
      placement:
        constraints: [node.id == kilqc94pi2upzvabttikrfr5d]
      restart_policy:
        condition: none
    networks:
      nw_swarm:

  cluster:
    image: $CENTOS7
    environment:
      - CENTOS
      - CI_ENVIRONMENT_NAME
      - CI_API_V4_URL
      - CI_REPOSITORY_URL
      - CI_PROJECT_ID
      - CI_PROJECT_URL
      - CI_PROJECT_PATH
      - CI_PROJECT_NAME
      - CI_COMMIT_REF_NAME
      - CI_BIN_DEPENDENCIES_JOB
    command: >
      sudo -u myusername -H /bin/bash -c ". /etc/profile &&
        mkdir -p /storage1/$CI_COMMIT_REF_NAME/$CI_PROJECT_NAME &&
        cd /storage1/$CI_COMMIT_REF_NAME/$CI_PROJECT_NAME &&
            git clone -b $CI_COMMIT_REF_NAME $CI_REPOSITORY_URL . &&
            curl $CI_API_V4_URL/projects/$CI_PROJECT_ID/jobs/artifacts/$CI_COMMIT_REF_NAME/download?job=$CI_BIN_DEPENDENCIES_JOB -o artifacts.zip &&
            unzip artifacts.zip ;
        cd /storage1/$CI_COMMIT_REF_NAME/$CI_PROJECT_NAME/scripts/deploy/ &&
            python3 createconfig.py -s $CI_ENVIRONMENT_NAME &&
            /bin/bash install_venv.sh -d -r ../../requirements.txt &&
            python3 prepare_init.d.py &&
            python3 deploy.py -s $CI_ENVIRONMENT_NAME"
    deploy:
      replicas: 1
      placement:
        constraints: [node.id == kilqc94pi2upzvabttikrfr5d]
      restart_policy:
        condition: none
    tty: true
    stdin_open: true
    networks:
      nw_swarm:

networks:
  nw_swarm:
    external: true

මෙහිදී ඔබට සංරචක එක් ජාලයකින් (nw_swarm) සම්බන්ධ වී ඇති අතර ඒවා එකිනෙකට ලබා ගත හැකිය.

පද්ධති සංරචක (redis, mysql මත පදනම්ව) සාමාන්‍ය අභිරුචි සංරචක සංචිතයෙන් වෙන් කරනු ලැබේ (සැලසුම් සහ අභිරුචි ඒවා සේවා ලෙස බෙදා ඇත). අපගේ පොකුරේ යෙදවීමේ අදියර අපගේ එක් විශාල වින්‍යාසගත රූපයකට CMD ලබා දීමක් සේ පෙනෙන අතර, සාමාන්‍යයෙන්, I කොටසෙහි විස්තර කර ඇති යෙදවුමෙන් ප්‍රායෝගිකව වෙනස් නොවේ. මම වෙනස්කම් ඉස්මතු කරමි:

  • git clone... - යෙදවීමට අවශ්‍ය ගොනු ලබා ගන්න (createconfig.py, install_venv.sh, ආදිය)
  • curl... && unzip... - ගොඩනඟන ලද කෞතුක වස්තු බාගත කර ඉවත් කරන්න (සම්පාදනය කරන ලද උපයෝගිතා)

තවමත් විස්තර නොකළ එක් ගැටලුවක් පමණි: වෙබ් අතුරු මුහුණතක් ඇති සංරචක සංවර්ධකයින්ගේ බ්‍රව්සර්වලින් ප්‍රවේශ විය නොහැක. අපි ප්‍රතිලෝම ප්‍රොක්සි භාවිතයෙන් මෙම ගැටළුව විසඳන්නෙමු:

.gitlab-ci.yml හි, පොකුරු තොගය යෙදවීමෙන් පසු, අපි සමතුලිතය යෙදවීමේ රේඛාව එක් කරන්නෙමු (එය, කැප වූ විට, එහි වින්‍යාසය පමණක් යාවත්කාලීන කරයි (සැකිල්ලට අනුව නව nginx වින්‍යාස ගොනු නිර්මාණය කරයි: /etc/nginx/conf. d/${CI_COMMIT_REF_NAME}.conf) - docker-compose-nginx.yml කේතය බලන්න)

    - docker stack deploy -c docker-compose-nginx.yml ${CI_ENVIRONMENT_NAME} --with-registry-auth

docker-compose-nginx.yml

---
version: '3'

services:
  nginx:
    image: nginx:latest
    environment:
      CI_COMMIT_REF_NAME: ${CI_COMMIT_REF_NAME}
      NGINX_CONFIG: |-
            server {
                listen 8080;
                server_name staging_${CI_COMMIT_REF_NAME}_cluster.dev;

                location / {
                    proxy_pass http://staging_${CI_COMMIT_REF_NAME}_cluster:8080;
                }
            }
            server {
                listen 5555;
                server_name staging_${CI_COMMIT_REF_NAME}_cluster.dev;

                location / {
                    proxy_pass http://staging_${CI_COMMIT_REF_NAME}_cluster:5555;
                }
            }
    volumes:
      - /tmp/staging/nginx:/etc/nginx/conf.d
    command:
      /bin/bash -c "echo -e "$$NGINX_CONFIG" > /etc/nginx/conf.d/${CI_COMMIT_REF_NAME}.conf;
        nginx -g "daemon off;";
        /etc/init.d/nginx reload"
    ports:
      - 8080:8080
      - 5555:5555
      - 3000:3000
      - 443:443
      - 80:80
    deploy:
      replicas: 1
      placement:
        constraints: [node.id == kilqc94pi2upzvabttikrfr5d]
      restart_policy:
        condition: none
    networks:
      nw_swarm:

networks:
  nw_swarm:
    external: true

සංවර්ධන පරිගණකවල, යාවත්කාලීන කිරීම /etc/hosts; nginx වෙත url නියම කරන්න:

10.50.173.106 staging_BRANCH-1831_cluster.dev

එබැවින්, හුදකලා වේදිකා පොකුරු යෙදවීම ක්‍රියාවට නංවා ඇති අතර සංවර්ධකයින්ට දැන් ඔවුන්ගේ කාර්යයන් පරීක්ෂා කිරීමට ප්‍රමාණවත් ඕනෑම සංඛ්‍යාවකින් ඒවා ක්‍රියාත්මක කළ හැකිය.

අනාගත සැලැස්ම:

  • අපගේ සංරචක සේවා ලෙස වෙන් කරන්න
  • එක් එක් Dockerfile සඳහා තිබේ
  • තොගයේ අඩු පටවන ලද නෝඩ් ස්වයංක්‍රීයව හඳුනා ගන්න
  • නාම රටාව අනුව නෝඩ් සඳහන් කරන්න (ලිපියේ මෙන් id භාවිතා කරනවාට වඩා)
  • තොගය විනාශ වී ඇති බවට චෙක්පතක් එක් කරන්න
  • ...

සඳහා විශේෂ ස්තුතිය ලිපිය.

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

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