GitLab Shell Runner. ื”ืฉืงื” ืชื—ืจื•ืชื™ืช ืฉืœ ืฉื™ืจื•ืชื™ื ืฉื ื‘ื“ืงื• ื‘ืืžืฆืขื•ืช Docker Compose

GitLab Shell Runner. ื”ืฉืงื” ืชื—ืจื•ืชื™ืช ืฉืœ ืฉื™ืจื•ืชื™ื ืฉื ื‘ื“ืงื• ื‘ืืžืฆืขื•ืช Docker Compose

ืžืืžืจ ื–ื” ื™ืขื ื™ื™ืŸ ื‘ื•ื“ืงื™ื ื•ืžืคืชื—ื™ื ื›ืื—ื“, ืืš ืžื™ื•ืขื“ ื‘ืขื™ืงืจ ืœืžื•ืžื—ื™ ืื•ื˜ื•ืžืฆื™ื” ืืฉืจ ืžืชืžื•ื“ื“ื™ื ืขื ื‘ืขื™ื” ืฉืœ ื”ื’ื“ืจืช GitLab CI/CD ืœื‘ื“ื™ืงื•ืช ืื™ื ื˜ื’ืจืฆื™ื” ื‘ืชื ืื™ื ืฉืœ ืžืฉืื‘ื™ ืชืฉืชื™ืช ืœื ืžืกืคื™ืงื™ื ื•/ืื• ื”ื™ืขื“ืจ ืงื•ื ื˜ื™ื™ื ืจ ืคืœื˜ืคื•ืจืžืช ืชื–ืžื•ืจ. ืื ื™ ืื’ื™ื“ ืœืš ืื™ืš ืœื”ื’ื“ื™ืจ ืืช ื”ืคืจื™ืกื” ืฉืœ ืกื‘ื™ื‘ื•ืช ื‘ื“ื™ืงื” ื‘ืืžืฆืขื•ืช docker compose ื‘-Runner ืžืขื˜ืคืช GitLab ืื—ื“ ื•ื›ื“ื™ ืฉื‘ืคืจื™ืกื” ืฉืœ ืžืกืคืจ ืกื‘ื™ื‘ื•ืช, ื”ืฉื™ืจื•ืชื™ื ืฉื”ื•ืฉืงื• ืœื ื™ืคืจื™ืขื• ื–ื” ืœื–ื”.


ืชื•ื›ืŸ

ะŸั€ะตะดะฟะพัั‹ะปะบะธ

  1. ื‘ืชืจื’ื•ืœ ืฉืœื™, ืงืจื” ืœื ืคืขื ืฉื‘ื“ื™ืงื•ืช ืื™ื ื˜ื’ืจืฆื™ื” "ื˜ื•ืคืœื•" ื‘ืคืจื•ื™ืงื˜ื™ื. ื•ืœืขืชื™ื ืงืจื•ื‘ื•ืช ื”ื‘ืขื™ื” ื”ืจืืฉื•ื ื” ื•ื”ืžืฉืžืขื•ืชื™ืช ื‘ื™ื•ืชืจ ื”ื™ื ืฆื™ื ื•ืจ ื”-CI, ืฉื‘ื• ื‘ื“ื™ืงื•ืช ืื™ื ื˜ื’ืจืฆื™ื” ื‘ืคื™ืชื•ื— ื”ืฉื™ืจื•ืช(ื™ื) ืžืชื‘ืฆืข ื‘ืกื‘ื™ื‘ืช ืคื™ืชื•ื—/ืฉืœื‘. ื–ื” ื’ืจื ืœื ืžืขื˜ ื‘ืขื™ื•ืช:

    • ืขืงื‘ ืคื’ืžื™ื ื‘ืฉื™ืจื•ืช ืžืกื•ื™ื ื‘ืžื”ืœืš ื‘ื“ื™ืงืช ืื™ื ื˜ื’ืจืฆื™ื”, ืžืขื’ืœ ื”ื‘ื“ื™ืงื” ืขืœื•ืœ ืœื”ื™ื ื–ืง ืขืœ ื™ื“ื™ ื ืชื•ื ื™ื ืฉื‘ื•ืจื™ื. ื”ื™ื• ืžืงืจื™ื ืฉื‘ื”ื ืฉืœื™ื—ืช ื‘ืงืฉื” ืขื ืคื•ืจืžื˜ JSON ืฉื‘ื•ืจ ื’ืจืกื” ืืช ื”ืฉื™ืจื•ืช, ืžื” ืฉื”ืคืš ืืช ื”ืžืขืžื“ ืœื‘ืœืชื™ ืคืขื™ืœ ืœื—ืœื•ื˜ื™ืŸ.
    • ื”ืื˜ื” ืฉืœ โ€‹โ€‹ืžืขื’ืœ ื”ื‘ื“ื™ืงื” ื›ื›ืœ ืฉื ืชื•ื ื™ ื”ื‘ื“ื™ืงื” ื’ื“ืœื™ื. ืื ื™ ื—ื•ืฉื‘ ืฉื–ื” ืœื ื”ื’ื™ื•ื ื™ ืœืชืืจ ื“ื•ื’ืžื” ืขื ื ื™ืงื•ื™/ื”ื—ื–ืจืช ืžืกื“ ื”ื ืชื•ื ื™ื. ื‘ืชืจื’ื•ืœ ืฉืœื™, ืœื ื ืชืงืœืชื™ ื‘ืคืจื•ื™ืงื˜ ืฉื‘ื• ื”ืœื™ืš ื–ื” ืขื‘ืจ ื‘ืฆื•ืจื” ื—ืœืงื”.
    • ืกื™ื›ื•ืŸ ืฉืœ ืฉื™ื‘ื•ืฉ ื”ืคื•ื ืงืฆื™ื•ื ืœื™ื•ืช ืฉืœ ืžืขื’ืœ ื”ื‘ื“ื™ืงื” ื‘ืขืช ื‘ื“ื™ืงืช ื”ื’ื“ืจื•ืช ืžืขืจื›ืช ื›ืœืœื™ื•ืช. ืœื“ื•ื’ืžื”, ืžื“ื™ื ื™ื•ืช ืžืฉืชืžืฉ/ืงื‘ื•ืฆื”/ืกื™ืกืžื”/ื™ื™ืฉื•ื.
    • ื ืชื•ื ื™ ื‘ื“ื™ืงื” ืžื‘ื“ื™ืงื•ืช ืื•ื˜ื•ืžื˜ื™ื•ืช ืžืงืฉื™ื ืขืœ ื‘ื•ื“ืงื™ื ื™ื“ื ื™ื™ื.

    ื™ืฉ ืฉื™ื’ื™ื“ื• ืฉื‘ื“ื™ืงื•ืช ืื•ื˜ื•ืžื˜ื™ื•ืช ื˜ื•ื‘ื•ืช ืฆืจื™ื›ื•ืช ืœื ืงื•ืช ืืช ื”ื ืชื•ื ื™ื ืื—ืจื™ ืขืฆืžืŸ. ื™ืฉ ืœื™ ื˜ื™ืขื•ื ื™ื ื ื’ื“:

    • ืžืขืžื“ื™ื ื“ื™ื ืžื™ื™ื ื ื•ื—ื™ื ืžืื•ื“ ืœืฉื™ืžื•ืฉ.
    • ืœื ื›ืœ ืื•ื‘ื™ื™ืงื˜ ื ื™ืชืŸ ืœื”ืกื™ืจ ืžื”ืžืขืจื›ืช ื‘ืืžืฆืขื•ืช ื”-API. ืœื“ื•ื’ืžื”, ืงืจื™ืื” ืœืžื—ื™ืงืช ืื•ื‘ื™ื™ืงื˜ ืœื ื™ื•ืฉืžื” ืžื›ื™ื•ื•ืŸ ืฉื”ื™ื ืกื•ืชืจืช ืืช ื”ื”ื™ื’ื™ื•ืŸ ื”ืขืกืงื™.
    • ื‘ืขืช ื™ืฆื™ืจืช ืื•ื‘ื™ื™ืงื˜ ื“ืจืš ื”-API, ื ื™ืชืŸ ืœื™ืฆื•ืจ ื›ืžื•ืช ืขืฆื•ืžื” ืฉืœ ืžื˜ื-ื ืชื•ื ื™ื, ื“ื‘ืจ ืฉื‘ืขื™ื™ืชื™ ืœืžื—ื•ืง.
    • ืื ืœื‘ื“ื™ืงื•ืช ื™ืฉ ืชืœื•ืช ื‘ื™ื ืŸ ืœื‘ื™ืŸ ืขืฆืžืŸ, ืื– ืชื”ืœื™ืš ื ื™ืงื•ื™ ื”ื ืชื•ื ื™ื ืœืื—ืจ ื”ืคืขืœืช ื”ื‘ื“ื™ืงื•ืช ื”ื•ืคืš ืœื›ืื‘ ืจืืฉ.
    • ืงืจื™ืื•ืช ื ื•ืกืคื•ืช (ื•ืœื“ืขืชื™ ืœื ืžื•ืฆื“ืงื•ืช) ืœ-API.
    • ื•ื”ื˜ื™ืขื•ืŸ ื”ืžืจื›ื–ื™: ื›ืืฉืจ ืžืชื—ื™ืœื™ื ืœื ืงื•ืช ื ืชื•ื ื™ ื‘ื“ื™ืงื” ื™ืฉื™ืจื•ืช ืžืžืกื“ ื”ื ืชื•ื ื™ื. ื–ื” ื”ื•ืคืš ืœืงืจืงืก PK/FK ืืžื™ืชื™! ืื ื• ืฉื•ืžืขื™ื ืžืžืคืชื—ื™ื: "ื”ืจื’ืข ื”ื•ืกืคืชื™/ื”ืกืจืชื™/ืฉื™ื ื™ืชื™ ืฉืœื˜, ืœืžื” ื ืชืคืกื• 100500 ืžื‘ื—ื ื™ ืื™ื ื˜ื’ืจืฆื™ื”?"

    ืœื“ืขืชื™ ื”ืคืชืจื•ืŸ ื”ืื•ืคื˜ื™ืžืœื™ ื‘ื™ื•ืชืจ ื”ื•ื ืกื‘ื™ื‘ื” ื“ื™ื ืืžื™ืช.

  2. ืื ืฉื™ื ืจื‘ื™ื ืžืฉืชืžืฉื™ื ื‘-docer-compose ื›ื“ื™ ืœื”ืคืขื™ืœ ืกื‘ื™ื‘ืช ื‘ื“ื™ืงื”, ืืš ืžืขื˜ื™ื ืžืฉืชืžืฉื™ื ื‘-docer-compose ื‘ืขืช ื‘ื™ืฆื•ืข ื‘ื“ื™ืงื•ืช ืื™ื ื˜ื’ืจืฆื™ื” ื‘-CI/CD. ื•ื›ืืŸ ืื ื™ ืœื ืœื•ืงื— ื‘ื—ืฉื‘ื•ืŸ kubernetes, ื ื—ื™ืœ ื•ืคืœื˜ืคื•ืจืžื•ืช ืชื–ืžื•ืจ ืžื›ื•ืœื•ืช ืื—ืจื•ืช. ืœื ืœื›ืœ ื—ื‘ืจื” ื™ืฉ ืื•ืชื. ื–ื” ื™ื”ื™ื” ื ื—ืžื“ ืื docker-compose.yml ื”ื™ื” ืื•ื ื™ื‘ืจืกืœื™.
  3. ื’ื ืื ื™ืฉ ืœื ื• ืจืฅ QA ืžืฉืœื ื•, ืื™ืš ื ื•ื›ืœ ืœื•ื•ื“ื ืฉืฉื™ืจื•ืชื™ื ืฉื”ื•ืฉืงื• ื“ืจืš docker-compose ืœื ื™ืคืจื™ืขื• ื–ื” ืœื–ื”?
  4. ื›ื™ืฆื“ ืœืืกื•ืฃ ื™ื•ืžื ื™ื ืฉืœ ืฉื™ืจื•ืชื™ื ืฉื ื‘ื“ืงื•?
  5. ืื™ืš ืžื ืงื™ื ืืช ื”ืจืื ืจ?

ื™ืฉ ืœื™ ืจืฅ GitLab ืžืฉืœื™ ืขื‘ื•ืจ ื”ืคืจื•ื™ืงื˜ื™ื ืฉืœื™ ื•ื ืชืงืœืชื™ ื‘ืฉืืœื•ืช ื”ืืœื” ื‘ืžื”ืœืš ื”ืคื™ืชื•ื— ืœืงื•ื— Java ืขื‘ื•ืจ TestRail. ืœื™ืชืจ ื“ื™ื•ืง, ื‘ืขืช ื”ืคืขืœืช ืžื‘ื—ื ื™ ืื™ื ื˜ื’ืจืฆื™ื”. ืœื”ืœืŸ ื ืคืชื•ืจ ืืช ื”ื‘ืขื™ื•ืช ื”ืœืœื• ื‘ืืžืฆืขื•ืช ื“ื•ื’ืžืื•ืช ืžืคืจื•ื™ืงื˜ ื–ื”.

ืœืชื•ื›ืŸ

GitLab Shell Runner

ืขื‘ื•ืจ ืจืฅ, ืื ื™ ืžืžืœื™ืฅ ืขืœ ืžื›ื•ื ื” ื•ื™ืจื˜ื•ืืœื™ืช ืœื™ื ื•ืงืก ืขื 4 vCPU, 4 GB RAM, 50 GB HDD.
ื™ืฉ ื”ืจื‘ื” ืžื™ื“ืข ืขืœ ื”ื’ื“ืจืช gitlab-runner ื‘ืื™ื ื˜ืจื ื˜, ืื– ื‘ืงืฆืจื”:

  • ื”ืชื—ื‘ืจ ืœืžื›ื•ื ื” ื‘ืืžืฆืขื•ืช SSH
  • ืื ื™ืฉ ืœืš ืคื—ื•ืช ืž-8 GB RAM, ืื– ืื ื™ ืžืžืœื™ืฅ ืœืขืฉื•ืช ื”ื—ืœืคื” 10 GBื›ื“ื™ ืฉื”ืจื•ืฆื— OOM ืœื ื™ื‘ื•ื ื•ื™ื”ืจื•ื’ ืืช ื”ืžืฉื™ืžื•ืช ืฉืœื ื• ื‘ื’ืœืœ ื—ื•ืกืจ ื–ื™ื›ืจื•ืŸ RAM. ื–ื” ื™ื›ื•ืœ ืœืงืจื•ืช ื›ืืฉืจ ื™ื•ืชืจ ืž-5 ืžืฉื™ืžื•ืช ืžื•ืคืขืœื•ืช ื‘ื• ื–ืžื ื™ืช. ื”ืžืฉื™ืžื•ืช ื™ืชืงื“ืžื• ืœืื˜ ื™ื•ืชืจ, ืืš ื‘ื”ืชืžื“ื”.

    ื“ื•ื’ืžื” ืขื OOM killer

    ืื ืืชื” ืจื•ืื” ื‘ื™ื•ืžื ื™ ื”ืžืฉื™ืžื•ืช bash: line 82: 26474 Killed, ื•ืื– ืคืฉื•ื˜ ื‘ืฆืข ืขืœ ื”ืจืฅ sudo dmesg | grep 26474

    [26474]  1002 26474  1061935   123806     339        0             0 java
    Out of memory: Kill process 26474 (java) score 127 or sacrifice child
    Killed process 26474 (java) total-vm:4247740kB, anon-rss:495224kB, file-rss:0kB, shmem-rss:0kB

    ื•ืื ื”ืชืžื•ื ื” ื ืจืื™ืช ื‘ืขืจืš ื›ืš, ืื• ื”ื•ืกืฃ ื”ื—ืœืคื” ืื• ื”ื•ืกืฃ ื–ื™ื›ืจื•ืŸ RAM.

  • ืœื”ืชืงื™ืŸ gitlab-ranner, ืกึทื•ึธืจ, ื”ื“ื•ืงืจ-ื”ืœื—ื ื”, ืขืฉื”.
  • ื”ื•ืกืคืช ืžืฉืชืžืฉ gitlab-runner ืœืงื‘ื•ืฆื” docker
    sudo groupadd docker
    sudo usermod -aG docker gitlab-runner
  • ื”ื™ืจืฉื gitlab-ranner.
  • ืคืชื•ื— ืœืขืจื™ื›ื” /etc/gitlab-runner/config.toml ื•ืชื•ืกื™ืฃ

    concurrent=20
    [[runners]]
      request_concurrency = 10

    ื–ื” ื™ืืคืฉืจ ืœืš ืœื”ืจื™ืฅ ืžืฉื™ืžื•ืช ืžืงื‘ื™ืœื•ืช ืขืœ ืจืฅ ืื—ื“. ืงืจื ืขื•ื“ ื›ืืŸ.
    ืื ื™ืฉ ืœืš ืžื›ื•ื ื” ื—ื–ืงื” ื™ื•ืชืจ, ืœืžืฉืœ 8 vCPU, 16 GB RAM, ืื– ื ื™ืชืŸ ืœื”ื’ื“ื™ืœ ืืช ื”ืžืกืคืจื™ื ื”ืœืœื• ืœืคื—ื•ืช ืคื™ 2. ืื‘ืœ ื”ื›ืœ ืชืœื•ื™ ืžื” ื‘ื“ื™ื•ืง ื™ื•ืฉืง ืขืœ ื”ืจืื ืจ ื”ื–ื” ื•ื‘ืื™ื–ื” ื›ืžื•ืช.

ื–ื” ืžืกืคื™ืง.

ืœืชื•ื›ืŸ

ื”ื›ื ืช docker-compose.yml

ื”ืžืฉื™ืžื” ื”ืขื™ืงืจื™ืช ื”ื™ื docker-compose.yml ืื•ื ื™ื‘ืจืกืœื™ืช, ืืฉืจ ืžืคืชื—ื™ื/ื‘ื•ื“ืงื™ื ื™ื›ื•ืœื™ื ืœื”ืฉืชืžืฉ ื‘ื• ื”ืŸ ืžืงื•ืžื™ืช ื•ื”ืŸ ื‘ืฆื ืจืช ื”-CI.

ืงื•ื“ื ื›ืœ, ืื ื• ื™ื•ืฆืจื™ื ืฉืžื•ืช ืฉื™ืจื•ืช ื™ื™ื—ื•ื“ื™ื™ื ืขื‘ื•ืจ CI. ืื—ื“ ื”ืžืฉืชื ื™ื ื”ื™ื™ื—ื•ื“ื™ื™ื ื‘- GitLab CI ื”ื•ื ื”ืžืฉืชื ื” CI_JOB_ID. ืื ืชืคืจื˜ container_name ืขื ืžืฉืžืขื•ืช "service-${CI_JOB_ID:-local}", ืื– ื‘ืžืงืจื”:

  • ืื CI_JOB_ID ืœื ืžื•ื’ื“ืจ ื‘ืžืฉืชื ื™ ืกื‘ื™ื‘ื”,
    ืื– ืฉื ื”ืฉื™ืจื•ืช ื™ื”ื™ื” service-local
  • ืื CI_JOB_ID ืžื•ื’ื“ืจ ื‘ืžืฉืชื ื™ ืกื‘ื™ื‘ื” (ืœื“ื•ื’ืžื” 123),
    ืื– ืฉื ื”ืฉื™ืจื•ืช ื™ื”ื™ื” service-123

ืฉื ื™ืช, ืื ื• ื™ื•ืฆืจื™ื ืจืฉืช ืžืฉื•ืชืคืช ืœืฉื™ืจื•ืชื™ื ืฉื”ื•ืฉืงื•. ื–ื” ื ื•ืชืŸ ืœื ื• ื‘ื™ื“ื•ื“ ื‘ืจืžืช ื”ืจืฉืช ื‘ืขืช ื”ืคืขืœืช ืกื‘ื™ื‘ื•ืช ื‘ื“ื™ืงื” ืžืจื•ื‘ื•ืช.

networks:
  default:
    external:
      name: service-network-${CI_JOB_ID:-local}

ืœืžืขืฉื”, ื–ื” ื”ืฆืขื“ ื”ืจืืฉื•ืŸ ืœื”ืฆืœื—ื” =)

ื“ื•ื’ืžื” ืฉืœ docker-compose.yml ืฉืœื™ ืขื ื”ืขืจื•ืช

version: "3"

# ะ”ะปั ะบะพั€ั€ะตะบั‚ะฝะพะน ั€ะฐะฑะพั‚ั‹ web (php) ะธ fmt ะฝัƒะถะฝะพ, 
# ั‡ั‚ะพะฑั‹ ะบะพะฝั‚ะตะนะฝะตั€ั‹ ะธะผะตะปะธ ะพะฑั‰ะธะน ะธัะฟะพะปะฝัะตะผั‹ะน ะบะพะฝั‚ะตะฝั‚.
# ะ’ ะฝะฐัˆะตะผ ัะปัƒั‡ะฐะต, ัั‚ะพ ะดะธั€ะตะบั‚ะพั€ะธั /var/www/testrail
volumes:
  static-content:

# ะ˜ะทะพะปะธั€ัƒะตะผ ะพะบั€ัƒะถะตะฝะธะต ะฝะฐ ัะตั‚ะตะฒะพะผ ัƒั€ะพะฒะฝะต
networks:
  default:
    external:
      name: testrail-network-${CI_JOB_ID:-local}

services:
  db:
    image: mysql:5.7.22
    # ะšะฐะถะดั‹ะน container_name ัะพะดะตั€ะถะธั‚ ${CI_JOB_ID:-local}
    container_name: "testrail-mysql-${CI_JOB_ID:-local}"
    environment:
      MYSQL_HOST: db
      MYSQL_DATABASE: mydb
      MYSQL_ROOT_PASSWORD: 1234
      SKIP_GRANT_TABLES: 1
      SKIP_NETWORKING: 1
      SERVICE_TAGS: dev
      SERVICE_NAME: mysql
    networks:
    - default

  migration:
    image: registry.gitlab.com/touchbit/image/testrail/migration:latest
    container_name: "testrail-migration-${CI_JOB_ID:-local}"
    links:
    - db
    depends_on:
    - db
    networks:
    - default

  fpm:
    image: registry.gitlab.com/touchbit/image/testrail/fpm:latest
    container_name: "testrail-fpm-${CI_JOB_ID:-local}"
    volumes:
    - static-content:/var/www/testrail
    links:
    - db
    networks:
    - default

  web:
    image: registry.gitlab.com/touchbit/image/testrail/web:latest
    container_name: "testrail-web-${CI_JOB_ID:-local}"
    # ะ•ัะปะธ ะฟะตั€ะตะผะตะฝะฝั‹ะต TR_HTTP_PORT ะธะปะธ TR_HTTPS_PORTS ะฝะต ะพะฟั€ะตะดะตะปะตะฝั‹,
    # ั‚ะพ ัะตั€ะฒะธั ะฟะพะดะฝะธะผะฐะตั‚ัั ะฝะฐ 80 ะธ 443 ะฟะพั€ั‚ัƒ ัะพะพั‚ะฒะตั‚ัั‚ะฒะตะฝะฝะพ.
    ports:
      - ${TR_HTTP_PORT:-80}:80
      - ${TR_HTTPS_PORT:-443}:443
    volumes:
      - static-content:/var/www/testrail
    links:
      - db
      - fpm
    networks:
      - default

ื“ื•ื’ืžื” ืœืจื™ืฆื” ืžืงื•ืžื™ืช

docker-compose -f docker-compose.yml up -d
Starting   testrail-mysql-local     ... done
Starting   testrail-migration-local ... done
Starting   testrail-fpm-local       ... done
Recreating testrail-web-local       ... done

ืื‘ืœ ืœื ื”ื›ืœ ื›ืœ ื›ืš ืคืฉื•ื˜ ืขื ื”ืฉืงื” ื‘-CI.

ืœืชื•ื›ืŸ

ื”ื›ื ืช ื”-Makefile

ืื ื™ ืžืฉืชืžืฉ ื‘-Makefile ื›ื™ ื–ื” ืžืื•ื“ ื ื•ื— ื’ื ืœื ื™ื”ื•ืœ ืกื‘ื™ื‘ื” ืžืงื•ืžื™ืช ื•ื’ื ื‘-CI. ืขื•ื“ ืชื’ื•ื‘ื•ืช ื‘ืื™ื ื˜ืจื ื˜

# ะฃ ะผะตะฝั ะฒ ะฟั€ะพะตะบั‚ะฐั… ะฒัะต ะฒัะฟะพะผะพะณะฐั‚ะตะปัŒะฝั‹ะต ะฒะตั‰ะธ ะปะตะถะฐั‚ ะฒ ะดะธั€ะตะบั‚ะพั€ะธะธ `.indirect`,
# ะฒ ั‚ะพะผ ั‡ะธัะปะต ะธ `docker-compose.yml`

# ะ˜ัะฟะพะปัŒะทะพะฒะฐั‚ัŒ bash ั ะพะฟั†ะธะตะน pipefail 
# pipefail - ั„ะตะนะปะธั‚ ะฒั‹ะฟะพะปะฝะตะฝะธะต ะฟะฐะนะฟะฐ, ะตัะปะธ ะบะพะผะฐะฝะดะฐ ะฒั‹ะฟะพะปะฝะธะปะฐััŒ ั ะพัˆะธะฑะบะพะน
SHELL=/bin/bash -o pipefail

# ะžัั‚ะฐะฝะฐะฒะปะธะฒะฐะตะผ ะบะพะฝั‚ะตะนะฝะตั€ั‹ ะธ ัƒะดะฐะปัะตะผ ัะตั‚ัŒ
docker-kill:
    docker-compose -f $${CI_JOB_ID:-.indirect}/docker-compose.yml kill
    docker network rm network-$${CI_JOB_ID:-testrail} || true

# ะŸั€ะตะดะฒะฐั€ะธั‚ะตะปัŒะฝะพ ะฒั‹ะฟะพะปะฝัะตะผ docker-kill 
docker-up: docker-kill
    # ะกะพะทะดะฐะตะผ ัะตั‚ัŒ ะดะปั ะพะบั€ัƒะถะตะฝะธั 
    docker network create network-$${CI_JOB_ID:-testrail}
    # ะ—ะฐะฑะธั€ะฐะตะผ ะฟะพัะปะตะดะฝะธะต ะพะฑั€ะฐะทั‹ ะธะท docker-registry
    docker-compose -f $${CI_JOB_ID:-.indirect}/docker-compose.yml pull
    # ะ—ะฐะฟัƒัะบะฐะตะผ ะพะบั€ัƒะถะตะฝะธะต
    # force-recreate - ะฟั€ะธะฝัƒะดะธั‚ะตะปัŒะฝะพะต ะฟะตั€ะตัะพะทะดะฐะฝะธะต ะบะพะฝั‚ะตะนะฝะตั€ะพะฒ
    # renew-anon-volumes - ะฝะต ะธัะฟะพะปัŒะทะพะฒะฐั‚ัŒ volumes ะฟั€ะตะดั‹ะดัƒั‰ะธั… ะบะพะฝั‚ะตะนะฝะตั€ะพะฒ
    docker-compose -f $${CI_JOB_ID:-.indirect}/docker-compose.yml up --force-recreate --renew-anon-volumes -d
    # ะัƒ ะธ, ะฝะฐ ะฒััะบะธะน ัะปัƒั‡ะฐะน, ะฒั‹ะฒะตัั‚ะธ ั‡ั‚ะพ ั‚ะฐะผ ัƒ ะฝะฐั ะฒ ะฟั€ะธะฝั†ะธะฟะต ะทะฐะฟัƒั‰ะตะฝะพ ะฝะฐ ะผะฐัˆะธะฝะบะต
    docker ps

# ะšะพะปะปะตะบั‚ะธะผ ะปะพะณะธ ัะตั€ะฒะธัะพะฒ
docker-logs:
    mkdir ./logs || true
    docker logs testrail-web-$${CI_JOB_ID:-local}       >& logs/testrail-web.log
    docker logs testrail-fpm-$${CI_JOB_ID:-local}       >& logs/testrail-fpm.log
    docker logs testrail-migration-$${CI_JOB_ID:-local} >& logs/testrail-migration.log
    docker logs testrail-mysql-$${CI_JOB_ID:-local}     >& logs/testrail-mysql.log

# ะžั‡ะธัั‚ะบะฐ ั€ะฐะฝะฝะตั€ะฐ
docker-clean:
    @echo ะžัั‚ะฐะฝะฐะฒะปะธะฒะฐะตะผ ะฒัะต testrail-ะบะพะฝั‚ะตะนะฝะตั€ั‹
    docker kill $$(docker ps --filter=name=testrail -q) || true
    @echo ะžั‡ะธัั‚ะบะฐ ะดะพะบะตั€ ะบะพะฝั‚ะตะนะฝะตั€ะพะฒ
    docker rm -f $$(docker ps -a -f --filter=name=testrail status=exited -q) || true
    @echo ะžั‡ะธัั‚ะบะฐ dangling ะพะฑั€ะฐะทะพะฒ
    docker rmi -f $$(docker images -f "dangling=true" -q) || true
    @echo ะžั‡ะธัั‚ะบะฐ testrail ะพะฑั€ะฐะทะพะฒ
    docker rmi -f $$(docker images --filter=reference='registry.gitlab.com/touchbit/image/testrail/*' -q) || true
    @echo ะžั‡ะธัั‚ะบะฐ ะฒัะตั… ะฝะตะธัะฟะพะปัŒะทัƒะตะผั‹ั… volume
    docker volume rm -f $$(docker volume ls -q) || true
    @echo ะžั‡ะธัั‚ะบะฐ ะฒัะตั… testrail ัะตั‚ะตะน
    docker network rm $(docker network ls --filter=name=testrail -q) || true
    docker ps

ื‘ื“ื•ืง

ืœืขืฉื•ืช ื“ื•ืงืจ-ืืค

$ make docker-up 
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml kill
Killing testrail-web-local   ... done
Killing testrail-fpm-local   ... done
Killing testrail-mysql-local ... done
docker network rm network-${CI_JOB_ID:-testrail} || true
network-testrail
docker network create network-${CI_JOB_ID:-testrail}
d2ec063324081c8bbc1b08fd92242c2ea59d70cf4025fab8efcbc5c6360f083f
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml pull
Pulling db        ... done
Pulling migration ... done
Pulling fpm       ... done
Pulling web       ... done
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml up --force-recreate --renew-anon-volumes -d
Recreating testrail-mysql-local ... done
Recreating testrail-fpm-local       ... done
Recreating testrail-migration-local ... done
Recreating testrail-web-local       ... done
docker ps
CONTAINER ID  PORTS                                     NAMES
a845d3cb0e5a  0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp  testrail-web-local
19d8ef001398  9000/tcp                                  testrail-fpm-local
e28840a2369c  3306/tcp, 33060/tcp                       testrail-migration-local
0e7900c23f37  3306/tcp                                  testrail-mysql-local

ืœืขืฉื•ืช docker-logs

$ make docker-logs
mkdir ./logs || true
mkdir: cannot create directory โ€˜./logsโ€™: File exists
docker logs testrail-web-${CI_JOB_ID:-local}       >& logs/testrail-web.log
docker logs testrail-fpm-${CI_JOB_ID:-local}       >& logs/testrail-fpm.log
docker logs testrail-migration-${CI_JOB_ID:-local} >& logs/testrail-migration.log
docker logs testrail-mysql-${CI_JOB_ID:-local}     >& logs/testrail-mysql.log

GitLab Shell Runner. ื”ืฉืงื” ืชื—ืจื•ืชื™ืช ืฉืœ ืฉื™ืจื•ืชื™ื ืฉื ื‘ื“ืงื• ื‘ืืžืฆืขื•ืช Docker Compose

ืœืชื•ื›ืŸ

ื”ื›ื ืช .gitlab-ci.yml

ื”ืคืขืœืช ืžื‘ื—ื ื™ ืื™ื ื˜ื’ืจืฆื™ื”

Integration:
  stage: test
  tags:
    - my-shell-runner
  before_script:
    # ะัƒั‚ะตะฝั‚ะธั„ะธั†ะธั€ัƒะตะผัั ะฒ registry
    - docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY}
    # ะ“ะตะฝะตั€ะธั€ัƒะตะผ ะฟัะตะฒะดะพัƒะฝะธะบะฐะปัŒะฝั‹ะต TR_HTTP_PORT ะธ TR_HTTPS_PORT
    - export TR_HTTP_PORT=$(shuf -i10000-60000 -n1)
    - export TR_HTTPS_PORT=$(shuf -i10000-60000 -n1)
    # ัะพะทะดะฐะตะผ ะดะธั€ะตะบั‚ะพั€ะธัŽ ั ะธะดะตะฝั‚ะธั„ะธะบะฐั‚ะพั€ะพะผ ะทะฐะดะฐั‡ะธ
    - mkdir ${CI_JOB_ID}
    # ะบะพะฟะธั€ัƒะตะผ ะฒ ัะพะทะดะฐะฝะฝัƒัŽ ะดะธั€ะตะบั‚ะพั€ะธัŽ ะฝะฐัˆ docker-compose.yml
    # ั‡ั‚ะพะฑั‹ ะบะพะฝั‚ะตะบัั‚ ะฑั‹ะป ั€ะฐะทะฝั‹ะน ะดะปั ะบะฐะถะดะพะน ะทะฐะดะฐั‡ะธ
    - cp .indirect/docker-compose.yml ${CI_JOB_ID}/docker-compose.yml
  script:
    # ะฟะพะดะฝะธะผะฐะตะผ ะฝะฐัˆะต ะพะบั€ัƒะถะตะฝะธะต
    - make docker-up
    # ะทะฐะฟัƒัะบะฐะตะผ ั‚ะตัั‚ั‹ ะธัะฟะพะปะฝัะตะผั‹ะผ jar (ัƒ ะผะตะฝั ั‚ะฐะบ)
    - java -jar itest.jar --http-port ${TR_HTTP_PORT} --https-port ${TR_HTTPS_PORT}
    # ะธะปะธ ะฒ ะบะพะฝั‚ะตะนะฝะตั€ะต
    - docker run --network=testrail-network-${CI_JOB_ID:-local} --rm itest
  after_script:
    # ัะพะฑะธั€ะฐะตะผ ะปะพะณะธ
    - make docker-logs
    # ะพัั‚ะฐะฝะฐะฒะปะธะฒะฐะตะผ ะพะบั€ัƒะถะตะฝะธะต
    - make docker-kill
  artifacts:
    # ัะพั…ั€ะฐะฝัะตะผ ะปะพะณะธ
    when: always
    paths:
      - logs
    expire_in: 30 days

ื›ืชื•ืฆืื” ืžื”ืคืขืœืช ืžืฉื™ืžื” ื›ื–ื•, ืกืคืจื™ื™ืช ื”ื™ื•ืžื ื™ื ื‘-artifacts ืชื›ื™ืœ ื™ื•ืžื ื™ ืฉื™ืจื•ืช ื•ื‘ื“ื™ืงื•ืช. ื•ื–ื” ืžืื•ื“ ื ื•ื— ื‘ืžืงืจื” ืฉืœ ืฉื’ื™ืื•ืช. ื›ืœ ืžื‘ื—ืŸ ื‘ืžืงื‘ื™ืœ ื›ื•ืชื‘ ื™ื•ืžืŸ ืžืฉืœื•, ืื‘ืœ ืื ื™ ืื“ื‘ืจ ืขืœ ื–ื” ื‘ื ืคืจื“.

GitLab Shell Runner. ื”ืฉืงื” ืชื—ืจื•ืชื™ืช ืฉืœ ืฉื™ืจื•ืชื™ื ืฉื ื‘ื“ืงื• ื‘ืืžืฆืขื•ืช Docker Compose

ืœืชื•ื›ืŸ

ื ื™ืงื•ื™ ื”ืจืฅ

ื”ืžืฉื™ืžื” ืชื•ืคืขืœ ืจืง ืœืคื™ ืœื•ื— ื–ืžื ื™ื.

stages:
- clean
- build
- test

Clean runner:
  stage: clean
  only:
    - schedules
  tags:
    - my-shell-runner
  script:
    - make docker-clean

ืœืื—ืจ ืžื›ืŸ, ืขื‘ื•ืจ ืœืคืจื•ื™ืงื˜ GitLab ืฉืœื ื• -> CI/CD -> ืœื•ื—ื•ืช ื–ืžื ื™ื -> ืœื•ื— ื–ืžื ื™ื ื—ื“ืฉ ื•ื”ื•ืกืฃ ืœื•ื— ื–ืžื ื™ื ื—ื“ืฉ

GitLab Shell Runner. ื”ืฉืงื” ืชื—ืจื•ืชื™ืช ืฉืœ ืฉื™ืจื•ืชื™ื ืฉื ื‘ื“ืงื• ื‘ืืžืฆืขื•ืช Docker Compose

ืœืชื•ื›ืŸ

ืชื•ืฆืื”

ื”ืฉืงืช 4 ืžืฉื™ืžื•ืช ื‘- GitLab CI
GitLab Shell Runner. ื”ืฉืงื” ืชื—ืจื•ืชื™ืช ืฉืœ ืฉื™ืจื•ืชื™ื ืฉื ื‘ื“ืงื• ื‘ืืžืฆืขื•ืช Docker Compose

ื‘ื™ื•ืžื ื™ื ืฉืœ ื”ืžืฉื™ืžื” ื”ืื—ืจื•ื ื” ืขื ืžื‘ื—ื ื™ ืื™ื ื˜ื’ืจืฆื™ื” ืื ื• ืจื•ืื™ื ืงื•ื ื˜ื™ื™ื ืจื™ื ืžืžืฉื™ืžื•ืช ืฉื•ื ื•ืช

CONTAINER ID  NAMES
c6b76f9135ed  testrail-web-204645172
01d303262d8e  testrail-fpm-204645172
2cdab1edbf6a  testrail-migration-204645172
826aaf7c0a29  testrail-mysql-204645172
6dbb3fae0322  testrail-web-204645084
3540f8d448ce  testrail-fpm-204645084
70fea72aa10d  testrail-mysql-204645084
d8aa24b2892d  testrail-web-204644881
6d4ccd910fad  testrail-fpm-204644881
685d8023a3ec  testrail-mysql-204644881
1cdfc692003a  testrail-web-204644793
6f26dfb2683e  testrail-fpm-204644793
029e16b26201  testrail-mysql-204644793
c10443222ac6  testrail-web-204567103
04339229397e  testrail-fpm-204567103
6ae0accab28d  testrail-mysql-204567103
b66b60d79e43  testrail-web-204553690
033b1f46afa9  testrail-fpm-204553690
a8879c5ef941  testrail-mysql-204553690
069954ba6010  testrail-web-204553539
ed6b17d911a5  testrail-fpm-204553539
1a1eed057ea0  testrail-mysql-204553539

ื™ื•ืžืŸ ืžืคื•ืจื˜ ื™ื•ืชืจ

$ docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY}
WARNING! Using --password via the CLI is insecure. Use --password-stdin.
WARNING! Your password will be stored unencrypted in /home/gitlab-runner/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store
Login Succeeded
$ export TR_HTTP_PORT=$(shuf -i10000-60000 -n1)
$ export TR_HTTPS_PORT=$(shuf -i10000-60000 -n1)
$ mkdir ${CI_JOB_ID}
$ cp .indirect/docker-compose.yml ${CI_JOB_ID}/docker-compose.yml
$ make docker-up
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml kill
docker network rm testrail-network-${CI_JOB_ID:-local} || true
Error: No such network: testrail-network-204645172
docker network create testrail-network-${CI_JOB_ID:-local}
0a59552b4464b8ab484de6ae5054f3d5752902910bacb0a7b5eca698766d0331
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml pull
Pulling web       ... done
Pulling fpm       ... done
Pulling migration ... done
Pulling db        ... done
docker-compose -f ${CI_JOB_ID:-.indirect}/docker-compose.yml up --force-recreate --renew-anon-volumes -d
Creating volume "204645172_static-content" with default driver
Creating testrail-mysql-204645172 ... 
Creating testrail-mysql-204645172 ... done
Creating testrail-migration-204645172 ... done
Creating testrail-fpm-204645172       ... done
Creating testrail-web-204645172       ... done
docker ps
CONTAINER ID        IMAGE                                                          COMMAND                  CREATED              STATUS              PORTS                                           NAMES
c6b76f9135ed        registry.gitlab.com/touchbit/image/testrail/web:latest         "nginx -g 'daemon ofโ€ฆ"   13 seconds ago       Up 1 second         0.0.0.0:51148->80/tcp, 0.0.0.0:25426->443/tcp   testrail-web-204645172
01d303262d8e        registry.gitlab.com/touchbit/image/testrail/fpm:latest         "docker-php-entrypoiโ€ฆ"   16 seconds ago       Up 13 seconds       9000/tcp                                        testrail-fpm-204645172
2cdab1edbf6a        registry.gitlab.com/touchbit/image/testrail/migration:latest   "docker-entrypoint.sโ€ฆ"   16 seconds ago       Up 13 seconds       3306/tcp, 33060/tcp                             testrail-migration-204645172
826aaf7c0a29        mysql:5.7.22                                                   "docker-entrypoint.sโ€ฆ"   18 seconds ago       Up 16 seconds       3306/tcp                                        testrail-mysql-204645172
6dbb3fae0322        registry.gitlab.com/touchbit/image/testrail/web:latest         "nginx -g 'daemon ofโ€ฆ"   36 seconds ago       Up 22 seconds       0.0.0.0:44202->80/tcp, 0.0.0.0:20151->443/tcp   testrail-web-204645084
3540f8d448ce        registry.gitlab.com/touchbit/image/testrail/fpm:latest         "docker-php-entrypoiโ€ฆ"   38 seconds ago       Up 35 seconds       9000/tcp                                        testrail-fpm-204645084
70fea72aa10d        mysql:5.7.22                                                   "docker-entrypoint.sโ€ฆ"   40 seconds ago       Up 37 seconds       3306/tcp                                        testrail-mysql-204645084
d8aa24b2892d        registry.gitlab.com/touchbit/image/testrail/web:latest         "nginx -g 'daemon ofโ€ฆ"   About a minute ago   Up 53 seconds       0.0.0.0:31103->80/tcp, 0.0.0.0:43872->443/tcp   testrail-web-204644881
6d4ccd910fad        registry.gitlab.com/touchbit/image/testrail/fpm:latest         "docker-php-entrypoiโ€ฆ"   About a minute ago   Up About a minute   9000/tcp                                        testrail-fpm-204644881
685d8023a3ec        mysql:5.7.22                                                   "docker-entrypoint.sโ€ฆ"   About a minute ago   Up About a minute   3306/tcp                                        testrail-mysql-204644881
1cdfc692003a        registry.gitlab.com/touchbit/image/testrail/web:latest         "nginx -g 'daemon ofโ€ฆ"   About a minute ago   Up About a minute   0.0.0.0:44752->80/tcp, 0.0.0.0:23540->443/tcp   testrail-web-204644793
6f26dfb2683e        registry.gitlab.com/touchbit/image/testrail/fpm:latest         "docker-php-entrypoiโ€ฆ"   About a minute ago   Up About a minute   9000/tcp                                        testrail-fpm-204644793
029e16b26201        mysql:5.7.22                                                   "docker-entrypoint.sโ€ฆ"   About a minute ago   Up About a minute   3306/tcp                                        testrail-mysql-204644793
c10443222ac6        registry.gitlab.com/touchbit/image/testrail/web:latest         "nginx -g 'daemon ofโ€ฆ"   5 hours ago          Up 5 hours          0.0.0.0:57123->80/tcp, 0.0.0.0:31657->443/tcp   testrail-web-204567103
04339229397e        registry.gitlab.com/touchbit/image/testrail/fpm:latest         "docker-php-entrypoiโ€ฆ"   5 hours ago          Up 5 hours          9000/tcp                                        testrail-fpm-204567103
6ae0accab28d        mysql:5.7.22                                                   "docker-entrypoint.sโ€ฆ"   5 hours ago          Up 5 hours          3306/tcp                                        testrail-mysql-204567103
b66b60d79e43        registry.gitlab.com/touchbit/image/testrail/web:latest         "nginx -g 'daemon ofโ€ฆ"   5 hours ago          Up 5 hours          0.0.0.0:56321->80/tcp, 0.0.0.0:58749->443/tcp   testrail-web-204553690
033b1f46afa9        registry.gitlab.com/touchbit/image/testrail/fpm:latest         "docker-php-entrypoiโ€ฆ"   5 hours ago          Up 5 hours          9000/tcp                                        testrail-fpm-204553690
a8879c5ef941        mysql:5.7.22                                                   "docker-entrypoint.sโ€ฆ"   5 hours ago          Up 5 hours          3306/tcp                                        testrail-mysql-204553690
069954ba6010        registry.gitlab.com/touchbit/image/testrail/web:latest         "nginx -g 'daemon ofโ€ฆ"   5 hours ago          Up 5 hours          0.0.0.0:32869->80/tcp, 0.0.0.0:16066->443/tcp   testrail-web-204553539
ed6b17d911a5        registry.gitlab.com/touchbit/image/testrail/fpm:latest         "docker-php-entrypoiโ€ฆ"   5 hours ago          Up 5 hours          9000/tcp                                        testrail-fpm-204553539
1a1eed057ea0        mysql:5.7.22                                                   "docker-entrypoint.sโ€ฆ"   5 hours ago          Up 5 hours          3306/tcp                                        testrail-mysql-204553539

ื›ืœ ื”ืžืฉื™ืžื•ืช ื”ื•ืฉืœืžื• ื‘ื”ืฆืœื—ื”

ื—ืคืฆื™ ืžืฉื™ืžื” ืžื›ื™ืœื™ื ื™ื•ืžื ื™ ืฉื™ืจื•ืช ื•ื‘ื“ื™ืงื•ืช
GitLab Shell Runner. ื”ืฉืงื” ืชื—ืจื•ืชื™ืช ืฉืœ ืฉื™ืจื•ืชื™ื ืฉื ื‘ื“ืงื• ื‘ืืžืฆืขื•ืช Docker Compose

GitLab Shell Runner. ื”ืฉืงื” ืชื—ืจื•ืชื™ืช ืฉืœ ืฉื™ืจื•ืชื™ื ืฉื ื‘ื“ืงื• ื‘ืืžืฆืขื•ืช Docker Compose

ื”ื›ืœ ื ืจืื” ื™ืคื”, ืื‘ืœ ื™ืฉ ื ื™ื•ืื ืก. ื ื™ืชืŸ ืœื‘ื˜ืœ ืืช ื”ืฆื™ื ื•ืจ ื‘ื›ื•ื— ื‘ื–ืžืŸ ืฉืžื‘ื—ื ื™ ืื™ื ื˜ื’ืจืฆื™ื” ืคื•ืขืœื™ื, ื•ื‘ืžืงืจื” ื–ื” ืœื ื™ื•ืคืกืงื• ืžื™ื›ืœื™ื ื”ืคื•ืขืœื™ื. ืžื“ื™ ืคืขื ืืชื” ืฆืจื™ืš ืœื ืงื•ืช ืืช ื”ืจืื ืจ. ืœืจื•ืข ื”ืžื–ืœ, ื”ืžืฉื™ืžื” ืœืฉื™ืคื•ืจ ื‘-GitLab CE ืขื“ื™ื™ืŸ ื‘ืกื˜ื˜ื•ืก ืœื”ืจื—ื™ื‘

ืื‘ืœ ื”ื•ืกืคื ื• ื”ืฉืงื” ืฉืœ ืžืฉื™ืžื” ืœืคื™ ืœื•ื— ื–ืžื ื™ื, ื•ืืฃ ืื—ื“ ืœื ืื•ืกืจ ืขืœื™ื ื• ืœื”ืคืขื™ืœ ืื•ืชื” ื™ื“ื ื™ืช.
ืขื‘ื•ืจ ืืœ ื”ืคืจื•ื™ืงื˜ ืฉืœื ื• -> CI/CD -> ืœื•ื—ื•ืช ื–ืžื ื™ื ื•ื”ืคืขืœ ืืช ื”ืžืฉื™ืžื” Clean runner

GitLab Shell Runner. ื”ืฉืงื” ืชื—ืจื•ืชื™ืช ืฉืœ ืฉื™ืจื•ืชื™ื ืฉื ื‘ื“ืงื• ื‘ืืžืฆืขื•ืช Docker Compose

ืกืš ื”ื›ืœ:

  • ื™ืฉ ืœื ื• ืจืฅ ืคื’ื– ืื—ื“.
  • ืื™ืŸ ื”ืชื ื’ืฉื•ื™ื•ืช ื‘ื™ืŸ ืžืฉื™ืžื•ืช ืœืกื‘ื™ื‘ื”.
  • ืื ื• ืžืคืขื™ืœื™ื ืžืฉื™ืžื•ืช ืขื ืžื‘ื—ื ื™ ืื™ื ื˜ื’ืจืฆื™ื” ื‘ืžืงื‘ื™ืœ.
  • ืืชื” ื™ื›ื•ืœ ืœื”ืจื™ืฅ ืžื‘ื—ื ื™ ืื™ื ื˜ื’ืจืฆื™ื” ื‘ืื•ืคืŸ ืžืงื•ืžื™ ืื• ื‘ืงื•ื ื˜ื™ื™ื ืจ.
  • ื™ื•ืžื ื™ ืฉื™ืจื•ืช ื•ื‘ื“ื™ืงื•ืช ื ืืกืคื™ื ื•ืžืฆื•ืจืคื™ื ืœืžืฉื™ืžืช ื”ืฆื™ื ื•ืจ.
  • ืืคืฉืจ ืœื ืงื•ืช ืืช ื”ืจืื ืจ ืžืชืžื•ื ื•ืช Docker ื™ืฉื ื•ืช.

ื–ืžืŸ ื”ื”ืชืงื ื” ื”ื•ื ~2 ืฉืขื•ืช.
ื–ื” ื”ื›ืœ, ื‘ืขืฆื. ืืฉืžื— ืœืงื‘ืœ ืžืฉื•ื‘.

ืœืชื•ื›ืŸ

ืžืงื•ืจ: www.habr.com

ื”ื•ืกืคืช ืชื’ื•ื‘ื”