GitLab เชถเซ‡เชฒ เชฐเชจเชฐ. เชกเซ‹เช•เชฐ เช•เชฎเซเชชเซ‹เชเชจเซ‹ เช‰เชชเชฏเซ‹เช— เช•เชฐเซ€เชจเซ‡ เชชเชฐเซ€เช•เซเชทเชฃ เช•เชฐเซ‡เชฒ เชธเซ‡เชตเชพเช“เชจเซเช‚ เชธเซเชชเชฐเซเชงเชพเชคเซเชฎเช• เชชเซเชฐเช•เซเชทเซ‡เชชเชฃ

GitLab เชถเซ‡เชฒ เชฐเชจเชฐ. เชกเซ‹เช•เชฐ เช•เชฎเซเชชเซ‹เชเชจเซ‹ เช‰เชชเชฏเซ‹เช— เช•เชฐเซ€เชจเซ‡ เชชเชฐเซ€เช•เซเชทเชฃ เช•เชฐเซ‡เชฒ เชธเซ‡เชตเชพเช“เชจเซเช‚ เชธเซเชชเชฐเซเชงเชพเชคเซเชฎเช• เชชเซเชฐเช•เซเชทเซ‡เชชเชฃ

เช† เชฒเซ‡เช– เชชเชฐเซ€เช•เซเชทเช•เซ‹ เช…เชจเซ‡ เชตเชฟเช•เชพเชธเช•เชฐเซเชคเชพเช“ เชฌเช‚เชจเซ‡ เชฎเชพเชŸเซ‡ เชฐเชธ เชงเชฐเชพเชตเชคเซ‹ เชนเชถเซ‡, เชชเชฐเช‚เชคเซ เชคเซ‡ เชฎเซเช–เซเชฏเชคเซเชตเซ‡ เช“เชŸเซ‹เชฎเซ‡เชถเชจ เชจเชฟเชทเซเชฃเชพเชคเซ‹ เชฎเชพเชŸเซ‡ เช›เซ‡ เชœเซ‡เช“ เช…เชชเซ‚เชฐเชคเชพ เชˆเชจเซเชซเซเชฐเชพเชธเซเชŸเซเชฐเช•เซเชšเชฐ เชธเช‚เชธเชพเชงเชจเซ‹เชจเซ€ เชธเซเชฅเชฟเชคเชฟเชฎเชพเช‚ เช…เชจเซ‡/เช…เชฅเชตเชพ เช•เชจเซเชŸเซ‡เชจเชฐเชจเซ€ เช—เซ‡เชฐเชนเชพเชœเชฐเซ€เชฎเชพเช‚ เชเช•เซ€เช•เชฐเชฃ เชชเชฐเซ€เช•เซเชทเชฃ เชฎเชพเชŸเซ‡ GitLab CI/CD เชธเซ‡เชŸ เช•เชฐเชตเชพเชจเซ€ เชธเชฎเชธเซเชฏเชพเชจเซ‹ เชธเชพเชฎเชจเซ‹ เช•เชฐเซ€ เชฐเชนเซเชฏเชพ เช›เซ‡. เช“เชฐเซเช•เซ‡เชธเซเชŸเซเชฐเซ‡เชถเชจ เชชเซเชฒเซ‡เชŸเชซเซ‹เชฐเซเชฎ. เชนเซเช‚ เชคเชฎเชจเซ‡ เช•เชนเซ€เชถ เช•เซ‡ เชเช• เชธเชฟเช‚เช—เชฒ เช—เชฟเชŸเชฒเซ‡เชฌ เชถเซ‡เชฒ เชฐเชจเชฐ เชชเชฐ เชกเซ‹เช•เชฐ เช•เชฎเซเชชเซ‹เชเชจเซ‹ เช‰เชชเชฏเซ‹เช— เช•เชฐเซ€เชจเซ‡ เชŸเซ‡เชธเซเชŸ เชเชจเซเชตเชพเชฏเชฐเซเชจเชฎเซ‡เชจเซเชŸเชจเซ€ เชœเชฎเชพเชตเชŸ เช•เซ‡เชตเซ€ เชฐเซ€เชคเซ‡ เชธเซ‡เชŸ เช•เชฐเชตเซ€ เช…เชจเซ‡ เชœเซ‡เชฅเซ€ เช•เชฐเซ€เชจเซ‡ เชœเซเชฏเชพเชฐเซ‡ เช…เชจเซ‡เช• เชเชจเซเชตเชพเชฏเชฐเซเชจเชฎเซ‡เชจเซเชŸเซเชธ เชกเชฟเชชเซเชฒเซ‹เชฏ เช•เชฐเซ€ เชฐเชนเซเชฏเชพ เชนเซ‹เชฏ, เชคเซเชฏเชพเชฐเซ‡ เชฒเซ‹เชจเซเชš เช•เชฐเชพเชฏเซ‡เชฒเซ€ เชธเซ‡เชตเชพเช“ เชเช•เชฌเซ€เชœเชพ เชธเชพเชฅเซ‡ เชฆเช–เชฒ เชจ เช•เชฐเซ‡.


เช…เชจเซเช•เซเชฐเชฎเชฃเชฟเช•เชพ

เชชเซƒเชทเซเช เชญเซ‚เชฎเชฟ

  1. เชฎเชพเชฐเซ€ เชชเซเชฐเซ‡เช•เซเชŸเชฟเชธเชฎเชพเช‚, เชเชตเซเช‚ เช˜เชฃเซ€เชตเชพเชฐ เชฌเชจเซเชฏเซเช‚ เช•เซ‡ เชเช•เซ€เช•เชฐเชฃ เชชเชฐเซ€เช•เซเชทเชฃ เชชเซเชฐเซ‹เชœเซ‡เช•เซเชŸเซเชธ เชชเชฐ "เชธเชพเชฐเชตเชพเชฐ" เช•เชฐเชตเชพเชฎเชพเช‚ เช†เชตเซเชฏเซเช‚ เชนเชคเซเช‚. เช…เชจเซ‡ เช˜เชฃเซ€เชตเชพเชฐ เชชเซเชฐเชฅเชฎ เช…เชจเซ‡ เชธเซŒเชฅเซ€ เชจเซ‹เช‚เชงเชชเชพเชคเซเชฐ เชธเชฎเชธเซเชฏเชพ เช CI เชชเชพเช‡เชชเชฒเชพเช‡เชจ เช›เซ‡, เชœเซ‡เชฎเชพเช‚ เชเช•เซ€เช•เชฐเชฃ เชชเชฐเซ€เช•เซเชทเชฃ เชตเชฟเช•เชธเชพเชตเชตเชพเชฎเชพเช‚ เช†เชตเซ€ เชฐเชนเซ€ เช›เซ‡ เชธเซ‡เชตเชพ(เช“) เชกเซ‡เชต/เชธเซเชŸเซ‡เชœ เชตเชพเชคเชพเชตเชฐเชฃเชฎเชพเช‚ เชนเชพเชฅ เชงเชฐเชตเชพเชฎเชพเช‚ เช†เชตเซ‡ เช›เซ‡. เช†เชจเชพ เช•เชพเชฐเชฃเซ‡ เชฅเซ‹เชกเซ€ เชธเชฎเชธเซเชฏเชพเช“ เชฅเชˆ:

    • เชเช•เซ€เช•เชฐเชฃ เชชเชฐเซ€เช•เซเชทเชฃ เชฆเชฐเชฎเชฟเชฏเชพเชจ เชšเซ‹เช•เซเช•เชธ เชธเซ‡เชตเชพเชฎเชพเช‚ เช–เชพเชฎเซ€เชจเซ‡ เชฒเซ€เชงเซ‡, เชคเซ‚เชŸเซ‡เชฒเชพ เชกเซ‡เชŸเชพ เชฆเซเชตเชพเชฐเชพ เชชเชฐเซ€เช•เซเชทเชฃ เชธเชฐเซเช•เชฟเชŸเชจเซ‡ เชจเซเช•เชธเชพเชจ เชฅเชˆ เชถเช•เซ‡ เช›เซ‡. เชเชตเชพ เช•เชฟเชธเซเชธเชพเช“ เชนเชคเชพ เชœเซเชฏเชพเชฐเซ‡ เชคเซ‚เชŸเซ‡เชฒเชพ JSON เชซเซ‹เชฐเซเชฎเซ‡เชŸ เชธเชพเชฅเซ‡ เชตเชฟเชจเช‚เชคเซ€ เชฎเซ‹เช•เชฒเชตเชพเชฅเซ€ เชธเซ‡เชตเชพ เช•เซเชฐเซ‡เชถ เชฅเชˆ เช—เชˆ, เชœเซ‡เชฃเซ‡ เชธเซเชŸเซ‡เชจเซเชกเชจเซ‡ เชธเช‚เชชเซ‚เชฐเซเชฃเชชเชฃเซ‡ เช…เชฏเซ‹เช—เซเชฏ เชฌเชจเชพเชตเซเชฏเซเช‚.
    • เชŸเซ‡เชธเซเชŸ เชธเชฐเซเช•เชฟเชŸเชฎเชพเช‚ เชฎเช‚เชฆเซ€ เช•เชพเชฐเชฃ เช•เซ‡ เชŸเซ‡เชธเซเชŸ เชกเซ‡เชŸเชพ เชตเชงเซ‡ เช›เซ‡. เชฎเชจเซ‡ เชฒเชพเช—เซ‡ เช›เซ‡ เช•เซ‡ เชกเซ‡เชŸเชพเชฌเซ‡เชเชจเซ€ เชธเชซเชพเชˆ/เชฐเซ‹เชฒเชฟเช‚เช— เชฌเซ‡เช• เชธเชพเชฅเซ‡เชจเชพ เช‰เชฆเชพเชนเชฐเชฃเชจเซเช‚ เชตเชฐเซเชฃเชจ เช•เชฐเชตเชพเชจเซ‹ เช•เซ‹เชˆ เช…เชฐเซเชฅ เชจเชฅเซ€. เชฎเชพเชฐเซ€ เชชเซเชฐเซ‡เช•เซเชŸเชฟเชธเชฎเชพเช‚, เชฎเชจเซ‡ เชเชตเซ‹ เช•เซ‹เชˆ เชชเซเชฐเซ‹เชœเซ‡เช•เซเชŸ เชฎเชณเซเชฏเซ‹ เชจเชฅเซ€ เช•เซ‡ เชœเซเชฏเชพเช‚ เช† เชชเซเชฐเช•เซเชฐเชฟเชฏเชพ เชธเชฐเชณเชคเชพเชฅเซ€ เชฅเชˆ เชนเซ‹เชฏ.
    • เชธเชพเชฎเชพเชจเซเชฏ เชธเชฟเชธเซเชŸเชฎ เชธเซ‡เชŸเชฟเช‚เช—เซเชธเชจเซเช‚ เชชเชฐเซ€เช•เซเชทเชฃ เช•เชฐเชคเซ€ เชตเช–เชคเซ‡ เชชเชฐเซ€เช•เซเชทเชฃ เชธเชฐเซเช•เชฟเชŸเชจเซ€ เช•เชพเชฐเซเชฏเช•เซเชทเชฎเชคเชพเชฎเชพเช‚ เช–เชฒเซ‡เชฒ เชชเชนเซ‹เช‚เชšเชพเชกเชตเชพเชจเซเช‚ เชœเซ‹เช–เชฎ. เช‰เชฆเชพเชนเชฐเชฃ เชคเชฐเซ€เช•เซ‡, เชตเชชเชฐเชพเชถเช•เชฐเซเชคเชพ/เชœเซ‚เชฅ/เชชเชพเชธเชตเชฐเซเชก/เชเชชเซเชฒเชฟเช•เซ‡เชถเชจ เชจเซ€เชคเชฟ.
    • เชธเซเชตเชšเชพเชฒเชฟเชค เชชเชฐเซ€เช•เซเชทเชฃเซ‹เชฎเชพเช‚เชฅเซ€ เชŸเซ‡เชธเซเชŸ เชกเซ‡เชŸเชพ เชฎเซ‡เชจเซเชฏเซเช…เชฒ เชŸเซ‡เชธเซเชŸเชฐเซเชธ เชฎเชพเชŸเซ‡ เชœเซ€เชตเชจ เชฎเซเชถเซเช•เซ‡เชฒ เชฌเชจเชพเชตเซ‡ เช›เซ‡.

    เช•เซ‡เชŸเชฒเชพเช• เช•เชนเซ‡เชถเซ‡ เช•เซ‡ เชธเชพเชฐเชพ เช“เชŸเซ‹เชŸเซ‡เชธเซเชŸเซเชธเซ‡ เชชเซ‹เชคเชพเชจเชพ เชชเช›เซ€ เชกเซ‡เชŸเชพ เชธเชพเชซ เช•เชฐเชตเซ‹ เชœเซ‹เชˆเช. เชฎเชพเชฐเซ€ เชธเชพเชฎเซ‡ เชฆเชฒเซ€เชฒเซ‹ เช›เซ‡:

    • เชกเชพเชฏเชจเซ‡เชฎเชฟเช• เชธเซเชŸเซ‡เชจเซเชก เชตเชพเชชเชฐเชตเชพ เชฎเชพเชŸเซ‡ เช–เซ‚เชฌ เชœ เช…เชจเซเช•เซ‚เชณ เช›เซ‡.
    • เชฆเชฐเซ‡เช• เช‘เชฌเซเชœเซ‡เช•เซเชŸ API เชฆเซเชตเชพเชฐเชพ เชธเชฟเชธเซเชŸเชฎเชฎเชพเช‚เชฅเซ€ เชฆเซ‚เชฐ เช•เชฐเซ€ เชถเช•เชพเชคเชพ เชจเชฅเซ€. เช‰เชฆเชพเชนเชฐเชฃ เชคเชฐเซ€เช•เซ‡, เช‘เชฌเซเชœเซ‡เช•เซเชŸเชจเซ‡ เช•เชพเชขเซ€ เชจเชพเช–เชตเชพเชจเซ‹ เช•เซ‰เชฒ เช…เชฎเชฒเชฎเชพเช‚ เช†เชตเซเชฏเซ‹ เชจ เชนเชคเซ‹ เช•เชพเชฐเชฃ เช•เซ‡ เชคเซ‡ เชตเซเชฏเชตเชธเชพเชฏเชจเชพ เชคเชฐเซเช•เชจเซ‹ เชตเชฟเชฐเซ‹เชงเชพเชญเชพเชธ เช•เชฐเซ‡ เช›เซ‡.
    • API เชฆเซเชตเชพเชฐเชพ เช‘เชฌเซเชœเซ‡เช•เซเชŸ เชฌเชจเชพเชตเชคเซ€ เชตเช–เชคเซ‡, เชฎเซ‡เชŸเชพเชกเซ‡เชŸเชพเชจเซ‹ เชตเชฟเชถเชพเชณ เชœเชฅเซเชฅเซ‹ เชฌเชจเชพเชตเซ€ เชถเช•เชพเชฏ เช›เซ‡, เชœเซ‡ เช•เชพเชขเซ€ เชจเชพเช–เชตเชพเชฎเชพเช‚ เชธเชฎเชธเซเชฏเชพเชฐเซ‚เชช เช›เซ‡.
    • เชœเซ‹ เชชเชฐเซ€เช•เซเชทเชฃเซ‹เชฎเชพเช‚ เชเช•เชฌเซ€เชœเชพ เชชเชฐ เชจเชฟเชฐเซเชญเชฐเชคเชพ เชนเซ‹เชฏ, เชคเซ‹ เชชเชฐเซ€เช•เซเชทเชฃเซ‹ เชšเชฒเชพเชตเซเชฏเชพ เชชเช›เซ€ เชกเซ‡เชŸเชพ เชธเชพเชซ เช•เชฐเชตเชพเชจเซ€ เชชเซเชฐเช•เซเชฐเชฟเชฏเชพ เชฎเชพเชฅเชพเชจเซ‹ เชฆเซเช–เชพเชตเซ‹ เชฌเชจเซ€ เชœเชพเชฏ เช›เซ‡.
    • API เชจเซ‡ เชตเชงเชพเชฐเชพเชจเชพ (เช…เชจเซ‡, เชฎเชพเชฐเชพ เชฎเชคเซ‡, เชตเชพเชœเชฌเซ€ เชจเชฅเซ€) เช•เซ‰เชฒเซเชธ.
    • เช…เชจเซ‡ เชฎเซเช–เซเชฏ เชฆเชฒเซ€เชฒ: เชœเซเชฏเชพเชฐเซ‡ เชŸเซ‡เชธเซเชŸ เชกเซ‡เชŸเชพ เชธเซ€เชงเชพ เชกเซ‡เชŸเชพเชฌเซ‡เชเชฎเชพเช‚เชฅเซ€ เชธเชพเชซ เชฅเชตเชพเชจเซเช‚ เชถเชฐเซ‚ เชฅเชพเชฏ เช›เซ‡. เช† เชตเชพเชธเซเชคเชตเชฟเช• เชชเซ€เช•เซ‡/เชเชซเช•เซ‡ เชธเชฐเซเช•เชธเชฎเชพเช‚ เชซเซ‡เชฐเชตเชพเชˆ เชฐเชนเซเชฏเซเช‚ เช›เซ‡! เช…เชฎเซ‡ เชตเชฟเช•เชพเชธเช•เชฐเซเชคเชพเช“ เชชเชพเชธเซ‡เชฅเซ€ เชธเชพเช‚เชญเชณเซ€เช เช›เซ€เช: "เชฎเซ‡เช‚ เชนเชฎเชฃเชพเช‚ เชœ เชเช• เชšเชฟเชนเซเชจ เช‰เชฎเซ‡เชฐเซเชฏเซเช‚/เชฆเซ‚เชฐ เช•เชฐเซเชฏเซเช‚/เชจเชพเชฎ เชฌเชฆเชฒเซเชฏเซเช‚, เชถเชพ เชฎเชพเชŸเซ‡ 100500 เชเช•เซ€เช•เชฐเชฃ เชชเชฐเซ€เช•เซเชทเชฃเซ‹ เชชเช•เชกเชพเชฏเชพ?"

    เชฎเชพเชฐเชพ เชฎเชคเซ‡, เชธเซŒเชฅเซ€ เชถเซเชฐเซ‡เชทเซเช  เช‰เช•เซ‡เชฒ เช เช—เชคเชฟเชถเซ€เชฒ เชตเชพเชคเชพเชตเชฐเชฃ เช›เซ‡.

  2. เช˜เชฃเชพ เชฒเซ‹เช•เซ‹ เชŸเซ‡เชธเซเชŸ เชเชจเซเชตเชพเชฏเชฐเซเชจเชฎเซ‡เชจเซเชŸ เชšเชฒเชพเชตเชตเชพ เชฎเชพเชŸเซ‡ เชกเซ‹เช•เชฐ-เช•เช‚เชชเซ‹เชเชจเซ‹ เช‰เชชเชฏเซ‹เช— เช•เชฐเซ‡ เช›เซ‡, เชชเชฐเช‚เชคเซ CI/CD เชฎเชพเช‚ เชเช•เซ€เช•เชฐเชฃ เชชเชฐเซ€เช•เซเชทเชฃ เชนเชพเชฅ เชงเชฐเชคเซ€ เชตเช–เชคเซ‡ เชฅเซ‹เชกเชพ เชฒเซ‹เช•เซ‹ เชกเซ‹เช•เชฐ-เช•เช‚เชชเซ‹เชเชจเซ‹ เช‰เชชเชฏเซ‹เช— เช•เชฐเซ‡ เช›เซ‡. เช…เชจเซ‡ เช…เชนเซ€เช‚ เชนเซเช‚ เช•เซเชฌเชฐเชจเซ‡เชŸเซเชธ, เชธเซเชตเซ‹เชฐเซเชฎ เช…เชจเซ‡ เช…เชจเซเชฏ เช•เชจเซเชŸเซ‡เชจเชฐ เช“เชฐเซเช•เซ‡เชธเซเชŸเซเชฐเซ‡เชถเชจ เชชเซเชฒเซ‡เชŸเชซเซ‹เชฐเซเชฎเชจเซ‡ เชงเซเชฏเชพเชจเชฎเชพเช‚ เชฒเชˆ เชฐเชนเซเชฏเซ‹ เชจเชฅเซ€. เชฆเชฐเซ‡เช• เช•เช‚เชชเชจเซ€ เชชเชพเชธเซ‡ เชคเซ‡ เชจเชฅเซ€. เชœเซ‹ docker-compose.yml เชธเชพเชฐเซเชตเชคเซเชฐเชฟเช• เชนเซ‹เชค เชคเซ‹ เชคเซ‡ เชธเชฐเชธ เชฐเชนเซ‡เชถเซ‡.
  3. เชœเซ‹ เช…เชฎเชพเชฐเซ€ เชชเชพเชธเซ‡ เช…เชฎเชพเชฐเซ‹ เชชเซ‹เชคเชพเชจเซ‹ QA เชฐเชจเชฐ เชนเซ‹เชฏ, เชคเซ‹ เชชเชฃ เช…เชฎเซ‡ เช•เซ‡เชตเซ€ เชฐเซ€เชคเซ‡ เช–เชพเชคเชฐเซ€ เช•เชฐเซ€ เชถเช•เซ€เช เช•เซ‡ เชกเซ‹เช•เชฐ-เช•เช‚เชชเซ‹เช เชฆเซเชตเชพเชฐเชพ เชถเชฐเซ‚ เช•เชฐเชตเชพเชฎเชพเช‚ เช†เชตเซ‡เชฒเซ€ เชธเซ‡เชตเชพเช“ เชเช•เชฌเซ€เชœเชพ เชธเชพเชฅเซ‡ เชฆเช–เชฒ เช•เชฐเชคเซ€ เชจเชฅเซ€?
  4. เชชเชฐเซ€เช•เซเชทเชฃ เช•เชฐเซ‡เชฒ เชธเซ‡เชตเชพเช“เชจเชพ เชฒเซ‹เช— เช•เซ‡เชตเซ€ เชฐเซ€เชคเซ‡ เชเช•เชคเซเชฐเชฟเชค เช•เชฐเชตเชพ?
  5. เชฆเซ‹เชกเชตเซ€เชฐเชจเซ‡ เช•เซ‡เชตเซ€ เชฐเซ€เชคเซ‡ เชธเชพเชซ เช•เชฐเชตเซเช‚?

เชฎเชพเชฐเชพ เชชเซเชฐเซ‹เชœเซ‡เช•เซเชŸเซเชธ เชฎเชพเชŸเซ‡ เชฎเชพเชฐเซ€ เชชเชพเชธเซ‡ เชฎเชพเชฐเซ€ เชชเซ‹เชคเชพเชจเซ€ GitLab เชฐเชจเชฐ เช›เซ‡ เช…เชจเซ‡ เชตเชฟเช•เชพเชธ เชฆเชฐเชฎเชฟเชฏเชพเชจ เชฎเชจเซ‡ เช† เชชเซเชฐเชถเซเชจเซ‹เชจเซ‹ เชธเชพเชฎเชจเซ‹ เช•เชฐเชตเซ‹ เชชเชกเซเชฏเซ‹ เชœเชพเชตเชพ เช•เซเชฒเชพเชฏเช‚เชŸ เชฎเชพเชŸเซ‡ เชŸเซ‡เชธเซเชŸเชฐเซ‡เชฒ. เชตเชงเซ เชธเซเชชเชทเซเชŸ เชฐเซ€เชคเซ‡, เชœเซเชฏเชพเชฐเซ‡ เชเช•เซ€เช•เชฐเชฃ เชชเชฐเซ€เช•เซเชทเชฃเซ‹ เชšเชฒเชพเชตเซ€ เชฐเชนเซเชฏเชพ เชนเซ‹เชฏ. เชจเซ€เชšเซ‡ เช…เชฎเซ‡ เช† เชชเซเชฐเซ‹เชœเซ‡เช•เซเชŸเชจเชพ เช‰เชฆเชพเชนเชฐเชฃเซ‹เชจเซ‹ เช‰เชชเชฏเซ‹เช— เช•เชฐเซ€เชจเซ‡ เช† เชฎเซเชฆเซเชฆเชพเช“เชจเซ‡ เชนเชฒ เช•เชฐเซ€เชถเซเช‚.

เชธเชพเชฎเช—เซเชฐเซ€ เชฎเชพเชŸเซ‡

GitLab เชถเซ‡เชฒ เชฐเชจเชฐ

เชฆเซ‹เชกเชตเซ€เชฐ เชฎเชพเชŸเซ‡, เชนเซเช‚ 4 vCPU, 4 GB RAM, 50 GB HDD เชธเชพเชฅเซ‡ Linux เชตเชฐเซเชšเซเชฏเซเช…เชฒ เชฎเชถเซ€เชจเชจเซ€ เชญเชฒเชพเชฎเชฃ เช•เชฐเซเช‚ เช›เซเช‚.
เช‡เชจเซเชŸเชฐเชจเซ‡เชŸ เชชเชฐ เช—เชฟเชŸเชฒเซ‡เชฌ-เชฐเชจเชฐ เชธเซ‡เชŸ เช•เชฐเชตเชพ เชตเชฟเชถเซ‡ เช˜เชฃเซ€ เชฌเชงเซ€ เชฎเชพเชนเชฟเชคเซ€ เช›เซ‡, เชคเซ‡เชฅเซ€ เชŸเซ‚เช‚เช•เชฎเชพเช‚:

  • SSH เชฆเซเชตเชพเชฐเชพ เชฎเชถเซ€เชจเชฎเชพเช‚ เชฒเซ‹เช—เชฟเชจ เช•เชฐเซ‹
  • เชœเซ‹ เชคเชฎเชพเชฐเซ€ เชชเชพเชธเซ‡ 8 เชœเซ€เชฌเซ€ เช•เชฐเชคเชพ เช“เช›เซ€ เชฐเซ‡เชฎ เช›เซ‡, เชคเซ‹ เชนเซเช‚ เชญเชฒเชพเชฎเชฃ เช•เชฐเซเช‚ เช›เซเช‚ เชธเซเชตเซ‡เชช 10 เชœเซ€เชฌเซ€ เช•เชฐเซ‹เชœเซ‡เชฅเซ€ RAM เชจเชพ เช…เชญเชพเชตเซ‡ OOM เช•เชฟเชฒเชฐ เช†เชตเซ€เชจเซ‡ เช…เชฎเชพเชฐเชพ เช•เชพเชฐเซเชฏเซ‹เชจเซ‡ เชจเชทเซเชŸ เชจ เช•เชฐเซ‡. เชœเซเชฏเชพเชฐเซ‡ เชเช•เชธเชพเชฅเซ‡ 5 เชฅเซ€ เชตเชงเซ เช•เชพเชฐเซเชฏเซ‹ เชถเชฐเซ‚ เช•เชฐเชตเชพเชฎเชพเช‚ เช†เชตเซ‡ เชคเซเชฏเชพเชฐเซ‡ เช†เชตเซเช‚ เชฅเชˆ เชถเช•เซ‡ เช›เซ‡. เช•เชพเชฐเซเชฏเซ‹ เชตเชงเซ เชงเซ€เชฐเซ‡ เชงเซ€เชฐเซ‡, เชชเชฐเช‚เชคเซ เชธเชคเชค เช†เช—เชณ เชตเชงเชถเซ‡.

    OOM เช•เชฟเชฒเชฐ เชธเชพเชฅเซ‡เชจเซเช‚ เช‰เชฆเชพเชนเชฐเชฃ

    เชœเซ‹ เชคเชฎเซ‡ เช•เชพเชฐเซเชฏ เชฒเซ‹เช—เชฎเชพเช‚ เชœเซเช“ เช›เซ‹ 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-เชฐเชจเชฐ, เช—เซ‹เชฆเซ€, เชกเซ‹เช•เชฐ-เช•เช‚เชชเซ‹เช, เชฌเชจเชพเชตเซ‹.
  • เชตเชชเชฐเชพเชถเช•เชฐเซเชคเชพ เช‰เชฎเซ‡เชฐเซ€ เชฐเชนเซเชฏเชพ เช›เซ€เช gitlab-runner เชœเซ‚เชฅเชจเซ‡ docker
    sudo groupadd docker
    sudo usermod -aG docker gitlab-runner
  • เชจเซ‹เช‚เชงเชฃเซ€ เช•เชฐเซ‹ gitlab-เชฐเชจเชฐ.
  • เชธเช‚เชชเชพเชฆเชจ เชฎเชพเชŸเซ‡ เช–เซ‹เชฒเซ‹ /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 เชจเซ‹ เช‰เชชเชฏเซ‹เช— เช•เชฐเซเช‚ เช›เซเช‚ เช•เชพเชฐเชฃ เช•เซ‡ เชคเซ‡ เชธเซเชฅเชพเชจเชฟเช• เชชเชฐเซเชฏเชพเชตเชฐเชฃ เชตเซเชฏเชตเชธเซเชฅเชพเชชเชจ เช…เชจเซ‡ 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

เชกเซ‹เช•เชฐ-เชฒเซ‹เช—เซเชธ เชฌเชจเชพเชตเซ‹

$ 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 เชถเซ‡เชฒ เชฐเชจเชฐ. เชกเซ‹เช•เชฐ เช•เชฎเซเชชเซ‹เชเชจเซ‹ เช‰เชชเชฏเซ‹เช— เช•เชฐเซ€เชจเซ‡ เชชเชฐเซ€เช•เซเชทเชฃ เช•เชฐเซ‡เชฒ เชธเซ‡เชตเชพเช“เชจเซเช‚ เชธเซเชชเชฐเซเชงเชพเชคเซเชฎเช• เชชเซเชฐเช•เซเชทเซ‡เชชเชฃ

เชธเชพเชฎเช—เซเชฐเซ€ เชฎเชพเชŸเซ‡

.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

เช†เชตเชพ เช•เชพเชฐเซเชฏเชจเซ‡ เชšเชฒเชพเชตเชตเชพเชจเชพ เชชเชฐเชฟเชฃเชพเชฎเซ‡, เช†เชฐเซเชŸเชฟเชซเซ‡เช•เซเชŸเซเชธเชฎเชพเช‚ เชฒเซ‰เช—เซเชธ เชกเชฟเชฐเซ‡เช•เซเชŸเชฐเซ€เชฎเชพเช‚ เชธเซ‡เชตเชพ เช…เชจเซ‡ เชชเชฐเซ€เช•เซเชทเชฃ เชฒเซ‰เช—เซเชธ เชนเชถเซ‡. เชœเซ‡ เชญเซ‚เชฒเซ‹เชจเชพ เช•เชฟเชธเซเชธเชพเชฎเชพเช‚ เช–เซ‚เชฌ เชœ เช…เชจเซเช•เซ‚เชณ เช›เซ‡. เชธเชฎเชพเช‚เชคเชฐ เชฆเชฐเซ‡เช• เชชเชฐเซ€เช•เซเชทเชฃ เชคเซ‡เชจเซ‹ เชชเซ‹เชคเชพเชจเซ‹ เชฒเซ‹เช— เชฒเช–เซ‡ เช›เซ‡, เชชเชฐเช‚เชคเซ เชนเซเช‚ เช† เชตเชฟเชถเซ‡ เช…เชฒเช—เชฅเซ€ เชตเชพเชค เช•เชฐเซ€เชถ.

GitLab เชถเซ‡เชฒ เชฐเชจเชฐ. เชกเซ‹เช•เชฐ เช•เชฎเซเชชเซ‹เชเชจเซ‹ เช‰เชชเชฏเซ‹เช— เช•เชฐเซ€เชจเซ‡ เชชเชฐเซ€เช•เซเชทเชฃ เช•เชฐเซ‡เชฒ เชธเซ‡เชตเชพเช“เชจเซเช‚ เชธเซเชชเชฐเซเชงเชพเชคเซเชฎเช• เชชเซเชฐเช•เซเชทเซ‡เชชเชฃ

เชธเชพเชฎเช—เซเชฐเซ€ เชฎเชพเชŸเซ‡

เชฆเซ‹เชกเชตเซ€เชฐเชจเซ€ เชธเชซเชพเชˆ

เช•เชพเชฐเซเชฏ เชเช• เชถเซ‡เชกเซเชฏเซ‚เชฒ เช…เชจเซเชธเชพเชฐ เชœ เชถเชฐเซ‚ เช•เชฐเชตเชพเชฎเชพเช‚ เช†เชตเชถเซ‡.

stages:
- clean
- build
- test

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

เช†เช—เชณ, เช…เชฎเชพเชฐเชพ GitLab เชชเซเชฐเซ‹เชœเซ‡เช•เซเชŸ -> CI/CD -> เชถเซ‡เชกเซเชฏเซ‚เชฒเซเชธ -> เชจเชตเซเช‚ เชถเซ‡เชกเซเชฏเซ‚เชฒ เชชเชฐ เชœเชพเช“ เช…เชจเซ‡ เชจเชตเซเช‚ เชถเซ‡เชกเซเชฏเซ‚เชฒ เช‰เชฎเซ‡เชฐเซ‹

GitLab เชถเซ‡เชฒ เชฐเชจเชฐ. เชกเซ‹เช•เชฐ เช•เชฎเซเชชเซ‹เชเชจเซ‹ เช‰เชชเชฏเซ‹เช— เช•เชฐเซ€เชจเซ‡ เชชเชฐเซ€เช•เซเชทเชฃ เช•เชฐเซ‡เชฒ เชธเซ‡เชตเชพเช“เชจเซเช‚ เชธเซเชชเชฐเซเชงเชพเชคเซเชฎเช• เชชเซเชฐเช•เซเชทเซ‡เชชเชฃ

เชธเชพเชฎเช—เซเชฐเซ€ เชฎเชพเชŸเซ‡

เชชเชฐเชฟเชฃเชพเชฎ

GitLab CI เชฎเชพเช‚ 4 เช•เชพเชฐเซเชฏเซ‹ เชถเชฐเซ‚ เช•เชฐเซ€ เชฐเชนเซเชฏเชพ เช›เซ€เช
GitLab เชถเซ‡เชฒ เชฐเชจเชฐ. เชกเซ‹เช•เชฐ เช•เชฎเซเชชเซ‹เชเชจเซ‹ เช‰เชชเชฏเซ‹เช— เช•เชฐเซ€เชจเซ‡ เชชเชฐเซ€เช•เซเชทเชฃ เช•เชฐเซ‡เชฒ เชธเซ‡เชตเชพเช“เชจเซเช‚ เชธเซเชชเชฐเซเชงเชพเชคเซเชฎเช• เชชเซเชฐเช•เซเชทเซ‡เชชเชฃ

เชเช•เซ€เช•เชฐเชฃ เชชเชฐเซ€เช•เซเชทเชฃเซ‹ เชธเชพเชฅเซ‡เชจเชพ เช›เซ‡เชฒเซเชฒเชพ เช•เชพเชฐเซเชฏเชจเชพ เชฒเซ‹เช—เชฎเชพเช‚ เช†เชชเชฃเซ‡ เชตเชฟเชตเชฟเชง เช•เชพเชฐเซเชฏเซ‹เชฎเชพเช‚เชฅเซ€ เช•เชจเซเชŸเซ‡เชจเชฐ เชœเซ‹เชˆเช เช›เซ€เช

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 เชถเซ‡เชฒ เชฐเชจเชฐ. เชกเซ‹เช•เชฐ เช•เชฎเซเชชเซ‹เชเชจเซ‹ เช‰เชชเชฏเซ‹เช— เช•เชฐเซ€เชจเซ‡ เชชเชฐเซ€เช•เซเชทเชฃ เช•เชฐเซ‡เชฒ เชธเซ‡เชตเชพเช“เชจเซเช‚ เชธเซเชชเชฐเซเชงเชพเชคเซเชฎเช• เชชเซเชฐเช•เซเชทเซ‡เชชเชฃ

GitLab เชถเซ‡เชฒ เชฐเชจเชฐ. เชกเซ‹เช•เชฐ เช•เชฎเซเชชเซ‹เชเชจเซ‹ เช‰เชชเชฏเซ‹เช— เช•เชฐเซ€เชจเซ‡ เชชเชฐเซ€เช•เซเชทเชฃ เช•เชฐเซ‡เชฒ เชธเซ‡เชตเชพเช“เชจเซเช‚ เชธเซเชชเชฐเซเชงเชพเชคเซเชฎเช• เชชเซเชฐเช•เซเชทเซ‡เชชเชฃ

เชฌเชงเซเช‚ เชธเซเช‚เชฆเชฐ เชฒเชพเช—เซ‡ เช›เซ‡, เชชเชฐเช‚เชคเซ เชคเซ‡เชฎเชพเช‚ เชเช• เชธเซ‚เช•เซเชทเซเชฎเชคเชพ เช›เซ‡. เชœเซเชฏเชพเชฐเซ‡ เชเช•เซ€เช•เชฐเชฃ เชชเชฐเซ€เช•เซเชทเชฃเซ‹ เชšเชพเชฒเซ€ เชฐเชนเซเชฏเชพ เชนเซ‹เชฏ เชคเซเชฏเชพเชฐเซ‡ เชชเชพเช‡เชชเชฒเชพเช‡เชจเชจเซ‡ เชฌเชณเชœเชฌเชฐเซ€เชฅเซ€ เชฐเชฆ เช•เชฐเซ€ เชถเช•เชพเชฏ เช›เซ‡, เช† เช•เชฟเชธเซเชธเชพเชฎเชพเช‚ เช•เชจเซเชŸเซ‡เชจเชฐ เชšเชพเชฒเชคเชพ เช…เชŸเช•เชพเชตเชตเชพเชฎเชพเช‚ เช†เชตเชถเซ‡ เชจเชนเซ€เช‚. เชธเชฎเชฏ เชธเชฎเชฏ เชชเชฐ เชคเชฎเชพเชฐเซ‡ เชฐเชจเชฐเชจเซ‡ เชธเชพเชซ เช•เชฐเชตเชพเชจเซ€ เชœเชฐเซ‚เชฐ เช›เซ‡. เช•เชฎเชจเชธเซ€เชฌเซ‡, GitLab CE เชฎเชพเช‚ เชธเซเชงเชพเชฐเชฃเชพ เชฎเชพเชŸเซ‡เชจเซเช‚ เช•เชพเชฐเซเชฏ เชนเชœเซ เชชเชฃ เชธเซเชฅเชฟเชคเชฟเชฎเชพเช‚ เช›เซ‡ เช“เชชเชจ

เชชเชฐเช‚เชคเซ เช…เชฎเซ‡ เชเช• เชถเซ‡เชกเซเชฏเซ‚เชฒ เชฎเซเชœเชฌ เช•เชพเชฐเซเชฏเชจเซเช‚ เชฒเซ‹เชจเซเชšเชฟเช‚เช— เช‰เชฎเซ‡เชฐเซเชฏเซเช‚ เช›เซ‡, เช…เชจเซ‡ เชคเซ‡เชจเซ‡ เชฎเซ‡เชจเซเชฏเซเช…เชฒเซ€ เชšเชฒเชพเชตเชตเชพ เชฎเชพเชŸเซ‡ เช…เชฎเชจเซ‡ เช•เซ‹เชˆ เชฎเชจเชพเชˆ เช•เชฐเชคเซเช‚ เชจเชฅเซ€.
เช…เชฎเชพเชฐเชพ เชชเซเชฐเซ‹เชœเซ‡เช•เซเชŸ -> CI/CD -> เชถเซ‡เชกเซเชฏเซเชฒเซเชธ เชชเชฐ เชœเชพเช“ เช…เชจเซ‡ เช•เชพเชฐเซเชฏ เชšเชฒเชพเชตเซ‹ Clean runner

GitLab เชถเซ‡เชฒ เชฐเชจเชฐ. เชกเซ‹เช•เชฐ เช•เชฎเซเชชเซ‹เชเชจเซ‹ เช‰เชชเชฏเซ‹เช— เช•เชฐเซ€เชจเซ‡ เชชเชฐเซ€เช•เซเชทเชฃ เช•เชฐเซ‡เชฒ เชธเซ‡เชตเชพเช“เชจเซเช‚ เชธเซเชชเชฐเซเชงเชพเชคเซเชฎเช• เชชเซเชฐเช•เซเชทเซ‡เชชเชฃ

เช•เซเชฒ:

  • เช…เชฎเชพเชฐเซ€ เชชเชพเชธเซ‡ เชเช• เชถเซ‡เชฒ เชฐเชจเชฐ เช›เซ‡.
  • เช•เชพเชฐเซเชฏเซ‹ เช…เชจเซ‡ เชชเชฐเซเชฏเชพเชตเชฐเชฃ เชตเชšเซเชšเซ‡ เช•เซ‹เชˆ เชคเช•เชฐเชพเชฐ เชจเชฅเซ€.
  • เช…เชฎเซ‡ เชธเชฎเชพเช‚เชคเชฐ เชเช•เซ€เช•เชฐเชฃ เชชเชฐเซ€เช•เซเชทเชฃเซ‹ เชธเชพเชฅเซ‡ เช•เชพเชฐเซเชฏเซ‹ เชšเชฒเชพเชตเซ€เช เช›เซ€เช.
  • เชคเชฎเซ‡ เชธเซเชฅเชพเชจเชฟเช• เชฐเซ€เชคเซ‡ เช…เชฅเชตเชพ เช•เชจเซเชŸเซ‡เชจเชฐเชฎเชพเช‚ เชเช•เซ€เช•เชฐเชฃ เชชเชฐเซ€เช•เซเชทเชฃเซ‹ เชšเชฒเชพเชตเซ€ เชถเช•เซ‹ เช›เซ‹.
  • เชธเซ‡เชตเชพ เช…เชจเซ‡ เชชเชฐเซ€เช•เซเชทเชฃ เชฒเซ‹เช— เชเช•เชคเซเชฐเชฟเชค เช•เชฐเชตเชพเชฎเชพเช‚ เช†เชตเซ‡ เช›เซ‡ เช…เชจเซ‡ เชชเชพเช‡เชชเชฒเชพเช‡เชจ เช•เชพเชฐเซเชฏ เชธเชพเชฅเซ‡ เชœเซ‹เชกเชพเชฏเซ‡เชฒ เช›เซ‡.
  • เชœเซ‚เชจเซ€ เชกเซ‹เช•เชฐ เชˆเชฎเซ‡เชœเซ‹เชฎเชพเช‚เชฅเซ€ เชฐเชจเชฐเชจเซ‡ เชธเชพเชซ เช•เชฐเชตเซเช‚ เชถเช•เซเชฏ เช›เซ‡.

เชธเซ‡เชŸเช…เชช เชธเชฎเชฏ ~2 เช•เชฒเชพเช• เช›เซ‡.
เชคเซ‡ เชฌเชงเชพ เช›เซ‡, เชตเชพเชธเซเชคเชตเชฎเชพเช‚. เชฎเชจเซ‡ เชชเซเชฐเชคเชฟเชธเชพเชฆ เชฎเซ‡เชณเชตเซ€เชจเซ‡ เช†เชจเช‚เชฆ เชฅเชถเซ‡.

เชธเชพเชฎเช—เซเชฐเซ€ เชฎเชพเชŸเซ‡

เชธเซ‹เชฐเซเชธ: www.habr.com

เชเช• เชŸเชฟเชชเซเชชเชฃเซ€ เช‰เชฎเซ‡เชฐเซ‹