Kubernetes เช•เซเชฒเชธเซเชŸเชฐเชฎเชพเช‚ เชœเซ‚เชจเซ€ เชธเซเชตเชฟเชงเชพ เชถเชพเช–เชพเชจเซ‡ เชฆเซ‚เชฐ เช•เชฐเซ€ เชฐเชนเซเชฏเชพเช‚ เช›เซ€เช

Kubernetes เช•เซเชฒเชธเซเชŸเชฐเชฎเชพเช‚ เชœเซ‚เชจเซ€ เชธเซเชตเชฟเชงเชพ เชถเชพเช–เชพเชจเซ‡ เชฆเซ‚เชฐ เช•เชฐเซ€ เชฐเชนเซเชฏเชพเช‚ เช›เซ€เช

เชนเซ‡เชฒเซ‹! เชซเซ€เชšเชฐ เชถเชพเช–เชพ (เช‰เชฐเซเชซ เชกเชฟเชชเซเชฒเซ‹เชฏ เชชเซเชฐเซ€เชตเซเชฏเซ‚, เชฐเชฟเชตเซเชฏเซ เชเชช) - เช† เชคเซเชฏเชพเชฐเซ‡ เชฅเชพเชฏ เช›เซ‡ เชœเซเชฏเชพเชฐเซ‡ เชฎเชพเชคเซเชฐ เชฎเชพเชธเซเชŸเชฐ เชฌเซเชฐเชพเชจเซเชš เชœ เชคเซˆเชจเชพเชค เช•เชฐเชตเชพเชฎเชพเช‚ เช†เชตเชคเซ€ เชจเชฅเซ€, เชชเชฐเช‚เชคเซ เชฆเชฐเซ‡เช• เชชเซเชฒ เชฐเชฟเช•เซเชตเซ‡เชธเซเชŸเชจเซ‡ เชเช• เช…เชจเชจเซเชฏ URL เชชเชฐ เชชเชฃ เชฎเซ‹เช•เชฒเชตเชพเชฎเชพเช‚ เช†เชตเซ‡ เช›เซ‡. เชคเชฎเซ‡ เชšเซ‡เช• เช•เชฐเซ€ เชถเช•เซ‹ เช›เซ‹ เช•เซ‡ เช•เซ‹เชก เชชเซเชฐเซ‹เชกเช•เซเชถเชจ เชเชจเซเชตเชพเชฏเชฐเซเชจเชฎเซ‡เชจเซเชŸเชฎเชพเช‚ เช•เชพเชฎ เช•เชฐเซ‡ เช›เซ‡ เช•เซ‡ เช•เซ‡เชฎ; เชธเซเชตเชฟเชงเชพ เช…เชจเซเชฏ เชชเซเชฐเซ‹เช—เซเชฐเชพเชฎเชฐเซ‹ เช…เชฅเชตเชพ เชชเซเชฐเซ‹เชกเช•เซเชŸ เชจเชฟเชทเซเชฃเชพเชคเซ‹เชจเซ‡ เชฌเชคเชพเชตเซ€ เชถเช•เชพเชฏ เช›เซ‡. เชœเซเชฏเชพเชฐเซ‡ เชคเชฎเซ‡ เชชเซเชฒ เชฐเชฟเช•เซเชตเซ‡เชธเซเชŸเชฎเชพเช‚ เช•เชพเชฎ เช•เชฐเซ€ เชฐเชนเซเชฏเชพ เชนเซ‹เชต, เชคเซเชฏเชพเชฐเซ‡ เชœเซ‚เชจเชพ เช•เซ‹เชก เชฎเชพเชŸเซ‡ เชฆเชฐเซ‡เช• เชจเชตเซ€ เช•เชฎเชฟเชŸ เชตเชฐเซเชคเชฎเชพเชจ เชœเชฎเชพเชตเชŸ เช•เชพเชขเซ€ เชจเชพเช–เชตเชพเชฎเชพเช‚ เช†เชตเซ‡ เช›เซ‡, เช…เชจเซ‡ เชจเชตเชพ เช•เซ‹เชก เชฎเชพเชŸเซ‡ เชจเชตเซ‹ เชœเชฎเชพเชตเชŸ เชฐเซ‹เชฒเช†เช‰เชŸ เช•เชฐเชตเชพเชฎเชพเช‚ เช†เชตเซ‡ เช›เซ‡. เชœเซเชฏเชพเชฐเซ‡ เชคเชฎเซ‡ เชฎเชพเชธเซเชŸเชฐ เชฌเซเชฐเชพเชจเซเชšเชฎเชพเช‚ เชชเซเชฒ เชฐเชฟเช•เซเชตเซ‡เชธเซเชŸ เชฎเชฐเซเชœ เช•เชฐเซ‹ เช›เซ‹ เชคเซเชฏเชพเชฐเซ‡ เชชเซเชฐเชถเซเชจเซ‹ เช‰เชญเชพ เชฅเชˆ เชถเช•เซ‡ เช›เซ‡. เชคเชฎเชจเซ‡ เชนเชตเซ‡ เชธเซเชตเชฟเชงเชพ เชถเชพเช–เชพเชจเซ€ เชœเชฐเซ‚เชฐ เชจเชฅเซ€, เชชเชฐเช‚เชคเซ เช•เซเชฌเชฐเชจเซ‡เชŸเซเชธ เชธเช‚เชธเชพเชงเชจเซ‹ เชนเชœเซ เชชเชฃ เช•เซเชฒเชธเซเชŸเชฐเชฎเชพเช‚ เช›เซ‡.

เชตเชฟเชถเซ‡เชทเชคเชพ เชถเชพเช–เชพเช“ เชตเชฟเชถเซ‡ เชตเชงเซ

เช•เซเชฌเชฐเชจเซ‡เชŸเซเชธเชฎเชพเช‚ เชตเชฟเชถเซ‡เชทเชคเชพ เชถเชพเช–เชพเช“ เชฌเชจเชพเชตเชตเชพเชจเซ‹ เชเช• เช…เชญเชฟเช—เชฎ เชจเซ‡เชฎเชธเซเชชเซ‡เชธเชจเซ‹ เช‰เชชเชฏเซ‹เช— เช•เชฐเชตเชพเชจเซ‹ เช›เซ‡. เชŸเซ‚เช‚เช•เชฎเชพเช‚, เช‰เชคเซเชชเชพเชฆเชจ เชฐเซ‚เชชเชฐเซ‡เช–เชพเช‚เช•เชจ เช†เชจเชพ เชœเซ‡เชตเซ‹ เชฆเซ‡เช–เชพเชฏ เช›เซ‡:

kind: Namespace
apiVersion: v1
metadata:
  name: habr-back-end
...

kind: Deployment
apiVersion: apps/v1
metadata:
  namespace: habr-back-end
spec:
  replicas: 3
...

เชซเซ€เชšเชฐ เชฌเซเชฐเชพเชจเซเชš เชฎเชพเชŸเซ‡, เชจเซ‡เชฎเชธเซเชชเซ‡เชธ เชคเซ‡เชจเชพ เช“เชณเช–เช•เชฐเซเชคเชพ (เช‰เชฆเชพเชนเชฐเชฃ เชคเชฐเซ€เช•เซ‡, เชชเซเชฒ เชฐเชฟเช•เซเชตเซ‡เชธเซเชŸ เชจเช‚เชฌเชฐ) เช…เชจเซ‡ เช…เชฎเซเช• เชชเซเชฐเช•เชพเชฐเชจเชพ เช‰เชชเชธเชฐเซเช—/เชชเซ‹เชธเซเชŸเชซเชฟเช•เซเชธ (เช‰เชฆเชพเชนเชฐเชฃ เชคเชฐเซ€เช•เซ‡,) เชธเชพเชฅเซ‡ เชฌเชจเชพเชตเชตเชพเชฎเชพเช‚ เช†เชตเซ‡ เช›เซ‡. -pr-):

kind: Namespace
apiVersion: v1
metadata:
  name: habr-back-end-pr-17
...

kind: Deployment
apiVersion: apps/v1
metadata:
  namespace: habr-back-end-pr-17
spec:
  replicas: 1
...

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

$ kubectl get pods --all-namespaces | grep -e "-pr-"
NAMESPACE            ... AGE
habr-back-end-pr-264 ... 4d8h
habr-back-end-pr-265 ... 5d7h

เช•เซเชฒเชธเซเชŸเชฐเชฎเชพเช‚ เชตเชฟเชถเซ‡เชทเชคเชพ เชถเชพเช–เชพเช“เชจเซ‡ เช•เซ‡เชตเซ€ เชฐเซ€เชคเซ‡ เชฒเชพเช—เซ เช•เชฐเชตเซ€ เชคเซ‡ เชตเชฟเชถเซ‡ เชคเชฎเซ‡ เชตเชพเช‚เชšเซ€ เชถเช•เซ‹ เช›เซ‹ เช…เชนเซ€เช‚ ะธ เช…เชนเซ€เช‚.

เชชเซเชฐเซ‹เชคเซเชธเชพเชนเชจ

เชšเชพเชฒเซ‹ เชธเชคเชค เชเช•เซ€เช•เชฐเชฃ เชธเชพเชฅเซ‡ เชฒเชพเช•เซเชทเชฃเชฟเช• เชชเซเชฒ เชตเชฟเชจเช‚เชคเซ€ เชœเซ€เชตเชจเชšเช•เซเชฐ เชœเซ‹เชˆเช (continuous integration):

  1. เช…เชฎเซ‡ เชถเชพเช–เชพเชฎเชพเช‚ เชจเชตเซ€ เช•เชฎเชฟเชŸเชฟเชจเซ‡ เชฆเชฌเชพเชฃ เช•เชฐเซ€เช เช›เซ€เช.
  2. เชฌเชฟเชฒเซเชก เชชเชฐ, เชฒเชฟเช‚เชŸเชฐ เช…เชจเซ‡/เช…เชฅเชตเชพ เชชเชฐเซ€เช•เซเชทเชฃเซ‹ เชšเชฒเชพเชตเชตเชพเชฎเชพเช‚ เช†เชตเซ‡ เช›เซ‡.
  3. Kubernetes เชชเซเชฒ เชตเชฟเชจเช‚เชคเซ€ เช—เซ‹เช เชตเชฃเซ€ เชซเซเชฒเชพเชฏ เชชเชฐ เชœเชจเชฐเซ‡เชŸ เชฅเชพเชฏ เช›เซ‡ (เช‰เชฆเชพเชนเชฐเชฃ เชคเชฐเซ€เช•เซ‡, เชคเซ‡เชจเซ‹ เชจเช‚เชฌเชฐ เชซเชฟเชจเชฟเชถเซเชก เชŸเซ‡เชฎเซเชชเชฒเซ‡เชŸเชฎเชพเช‚ เชฆเชพเช–เชฒ เช•เชฐเชตเชพเชฎเชพเช‚ เช†เชตเซ‡ เช›เซ‡).
  4. kubectl apply เชจเซ‹ เช‰เชชเชฏเซ‹เช— เช•เชฐเซ€เชจเซ‡, เชฐเซ‚เชชเชฐเซ‡เช–เชพเช‚เช•เชจเซ‹ เช•เซเชฒเชธเซเชŸเชฐเชฎเชพเช‚ เช‰เชฎเซ‡เชฐเชตเชพเชฎเชพเช‚ เช†เชตเซ‡ เช›เซ‡ (เชกเชฟเชชเซเชฒเซ‹เชฏ).
  5. เชชเซเชฒ เชตเชฟเชจเช‚เชคเซ€เชจเซ‡ เชฎเชพเชธเซเชŸเชฐ เชฌเซเชฐเชพเชจเซเชšเชฎเชพเช‚ เชฎเชฐเซเชœ เช•เชฐเชตเชพเชฎเชพเช‚ เช†เชตเซ‡ เช›เซ‡.

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

เช•เซ‡เชตเซ€ เชฐเซ€เชคเซ‡ เช‰เชชเชฏเซ‹เช— เช•เชฐเชตเซ‹

เชจเซ€เชšเซ‡เชจเชพ เช†เชฆเซ‡เชถ เชธเชพเชฅเซ‡ เชชเซเชฐเซ‹เชœเซ‡เช•เซเชŸ เช‡เชจเซเชธเซเชŸเซ‹เชฒ เช•เชฐเซ‹:

$ kubectl apply -f https://raw.githubusercontent.com/dmytrostriletskyi/stale-feature-branch-operator/master/configs/production.yml

เชจเซ€เชšเซ‡เชจเซ€ เชธเชพเชฎเช—เซเชฐเซ€ เชธเชพเชฅเซ‡ เชซเชพเช‡เชฒ เชฌเชจเชพเชตเซ‹ เช…เชจเซ‡ เชคเซ‡เชจเชพ เชฆเซเชตเชพเชฐเชพ เช‡เชจเซเชธเซเชŸเซ‹เชฒ เช•เชฐเซ‹ kubectl apply -f:

apiVersion: feature-branch.dmytrostriletskyi.com/v1
kind: StaleFeatureBranch
metadata:
  name: stale-feature-branch
spec:
  namespaceSubstring: -pr-
  afterDaysWithoutDeploy: 3

เชชเซ‡เชฐเชพเชฎเซ€เชŸเชฐ เชจเซ‡เชฎเชธเซเชชเซ‡เชธ เชธเชฌเชธเซเชŸเซเชฐเชฟเช‚เช— เช…เชจเซเชฏ เชจเซ‡เชฎเชธเซเชชเซ‡เชธเชฎเชพเช‚เชฅเซ€ เชชเซเชฒ เชตเชฟเชจเช‚เชคเซ€เช“ เชฎเชพเชŸเซ‡ เชจเซ‡เชฎเชธเซเชชเซ‡เชธ เชซเชฟเชฒเซเชŸเชฐ เช•เชฐเชตเชพ เชฎเชพเชŸเซ‡ เชœเชฐเซ‚เชฐเซ€ เช›เซ‡. เช‰เชฆเชพเชนเชฐเชฃ เชคเชฐเซ€เช•เซ‡, เชœเซ‹ เช•เซเชฒเชธเซเชŸเชฐเชฎเชพเช‚ เชจเซ€เชšเซ‡เชจเซ€ เชจเซ‡เชฎเชธเซเชชเซ‡เชธ เช›เซ‡: habr-back-end, habr-front-end, habr-back-end-pr-17, habr-back-end-pr-33, เชชเช›เซ€ เช•เชพเชขเซ€ เชจเชพเช–เชตเชพ เชฎเชพเชŸเซ‡ เช‰เชฎเซ‡เชฆเชตเชพเชฐเซ‹ เชนเชถเซ‡ habr-back-end-pr-17, habr-back-end-pr-33.

เชชเซ‡เชฐเชพเชฎเซ€เชŸเชฐ afterDaysWithoutDeploy เชœเซ‚เชจเซ€ เชจเซ‡เชฎเชธเซเชชเซ‡เชธ เช•เชพเชขเซ€ เชจเชพเช–เชตเชพเชจเซ€ เชœเชฐเซ‚เชฐ เช›เซ‡. เช‰เชฆเชพเชนเชฐเชฃ เชคเชฐเซ€เช•เซ‡, เชœเซ‹ เชจเซ‡เชฎเชธเซเชชเซ‡เชธ เชฌเชจเชพเชตเชตเชพเชฎเชพเช‚ เช†เชตเซ‡ เช›เซ‡ 3 ะดะฝั 1 ั‡ะฐั เชชเชพเช›เชณ, เช…เชจเซ‡ เชชเชฐเชฟเชฎเชพเชฃ เชธเซ‚เชšเชตเซ‡ เช›เซ‡ 3 ะดะฝั, เช† เชจเซ‡เชฎเชธเซเชชเซ‡เชธ เช•เชพเชขเซ€ เชจเชพเช–เชตเชพเชฎเชพเช‚ เช†เชตเชถเซ‡. เชœเซ‹ เชจเซ‡เชฎเชธเซเชชเซ‡เชธ เชฌเชจเชพเชตเชตเชพเชฎเชพเช‚ เช†เชตเซ‡ เชคเซ‹ เชคเซ‡ เชตเชฟเชฐเซเชฆเซเชง เชฆเชฟเชถเชพเชฎเชพเช‚ เชชเชฃ เช•เชพเชฎ เช•เชฐเซ‡ เช›เซ‡ 2 ะดะฝั 23 ั‡ะฐัะฐ เชชเชพเช›เชณ, เช…เชจเซ‡ เชชเชฐเชฟเชฎเชพเชฃ เชธเซ‚เชšเชตเซ‡ เช›เซ‡ 3 ะดะฝั, เช† เชจเซ‡เชฎเชธเซเชชเซ‡เชธ เช•เชพเชขเซ€ เชจเชพเช–เชตเชพเชฎเชพเช‚ เช†เชตเชถเซ‡ เชจเชนเซ€เช‚.

เชคเซเชฏเชพเช‚ เชเช• เชตเชงเซ เชชเชฐเชฟเชฎเชพเชฃ เช›เซ‡, เชคเซ‡ เชฌเชงเซ€ เชจเซ‡เชฎเชธเซเชชเซ‡เชธเชจเซ‡ เช•เซ‡เชŸเชฒเซ€ เชตเชพเชฐ เชธเซเช•เซ‡เชจ เช•เชฐเชตเชพ เช…เชจเซ‡ เชœเชฎเชพเชตเชŸ เชตเชฟเชจเชพ เชฆเชฟเชตเชธเซ‹ เชฎเชพเชŸเซ‡ เชคเชชเชพเชธเชตเชพ เชฎเชพเชŸเซ‡ เชœเชตเชพเชฌเชฆเชพเชฐ เช›เซ‡ - เชฆเชฐเซ‡เช• เชฎเชฟเชจเชฟเชŸ เชคเชชเชพเชธเซ‹. เชฎเซ‚เชณเชญเซ‚เชค เชฐเซ€เชคเซ‡ เชคเซ‡ เชธเชฎเชพเชจ เช›เซ‡ 30 ะผะธะฝัƒั‚ะฐะผ.

เช† เช•เซ‡เชตเซ€ เชฐเซ€เชคเซ‡ เช•เชพเชฎ เช•เชฐเซ‡ เช›เซ‡

เชตเซเชฏเชตเชนเชพเชฐเชฎเชพเช‚, เชคเชฎเชพเชฐเซ‡ เช†เชจเซ€ เชœเชฐเซ‚เชฐ เชชเชกเชถเซ‡:

  1. Docker เช…เชฒเช— เชตเชพเชคเชพเชตเชฐเชฃเชฎเชพเช‚ เช•เชพเชฎ เช•เชฐเชตเชพ เชฎเชพเชŸเซ‡.
  2. เชฎเชฟเชจเซ€เช•เซเชฌเซ‡ เชธเซเชฅเชพเชจเชฟเช• เชฐเซ€เชคเซ‡ เช•เซเชฌเชฐเชจเซ‡เชŸเซ€เชธ เช•เซเชฒเชธเซเชŸเชฐ เชŠเชญเซเช‚ เช•เชฐเชถเซ‡.
  3. kubectl - เช•เซเชฒเชธเซเชŸเชฐ เชฎเซ‡เชจเซ‡เชœเชฎเซ‡เชจเซเชŸ เชฎเชพเชŸเซ‡ เช•เชฎเชพเชจเซเชก เชฒเชพเช‡เชจ เช‡เชจเซเชŸเชฐเชซเซ‡เชธ.

เช…เชฎเซ‡ เชธเซเชฅเชพเชจเชฟเช• เชฐเซ€เชคเซ‡ เช•เซเชฌเชฐเชจเซ‡เชŸเซเชธ เช•เซเชฒเชธเซเชŸเชฐ เช‰เชญเชพ เช•เชฐเซ€เช เช›เซ€เช:

$ minikube start --vm-driver=docker
minikube v1.11.0 on Darwin 10.15.5
Using the docker driver based on existing profile.
Starting control plane node minikube in cluster minikube.

เชธเซ‚เชšเชตเซ‹ kubectl เชฎเซ‚เชณเชญเซ‚เชค เชฐเซ€เชคเซ‡ เชธเซเชฅเชพเชจเชฟเช• เช•เซเชฒเชธเซเชŸเชฐเชจเซ‹ เช‰เชชเชฏเซ‹เช— เช•เชฐเซ‹:

$ kubectl config use-context minikube
Switched to context "minikube".

เช‰เชคเซเชชเชพเชฆเชจ เชชเชฐเซเชฏเชพเชตเชฐเชฃ เชฎเชพเชŸเซ‡ เชฐเซ‚เชชเชฐเซ‡เช–เชพเช‚เช•เชจเซ‹ เชกเชพเช‰เชจเชฒเซ‹เชก เช•เชฐเซ‹:

$ curl https://raw.githubusercontent.com/dmytrostriletskyi/stale-feature-branch-operator/master/configs/production.yml > stale-feature-branch-production-configs.yml

เชชเซเชฐเซ‹เชกเช•เซเชถเชจ เชฐเซ‚เชชเชฐเซ‡เช–เชพเช‚เช•เชจเซ‹ เชœเซ‚เชจเชพ เชจเซ‡เชฎเชธเซเชชเซ‡เชธเชจเซ‡ เชšเช•เชพเชธเชตเชพ เชฎเชพเชŸเซ‡ เช—เซ‹เช เชตเซ‡เชฒ เชนเซ‹เชตเชพเชฅเซ€, เช…เชจเซ‡ เช…เชฎเชพเชฐเชพ เชจเชตเชพ เช‰เชญเชพ เชฅเชฏเซ‡เชฒเชพ เช•เซเชฒเชธเซเชŸเชฐเชฎเชพเช‚ เชคเซ‡ เชจเชฅเซ€, เช…เชฎเซ‡ เชชเชฐเซเชฏเชพเชตเชฐเชฃ เชตเซ‡เชฐเซ€เชเชฌเชฒเชจเซ‡ เชฌเชฆเชฒเซ€เชถเซเช‚. IS_DEBUG เชชเชฐ true. เช† เชฎเซ‚เชฒเซเชฏ เชธเชพเชฅเซ‡ เชชเชฐเชฟเชฎเชพเชฃ afterDaysWithoutDeploy เชงเซเชฏเชพเชจเชฎเชพเช‚ เชฒเซ‡เชตเชพเชฎเชพเช‚ เช†เชตเชคเซเช‚ เชจเชฅเซ€ เช…เชจเซ‡ เชœเชฎเชพเชตเชŸ เชตเช—เชฐเชจเชพ เชฆเชฟเชตเชธเซ‹ เชธเซเชงเซ€ เชจเซ‡เชฎเชธเซเชชเซ‡เชธเชจเซ€ เชคเชชเชพเชธ เช•เชฐเชตเชพเชฎเชพเช‚ เช†เชตเชคเซ€ เชจเชฅเซ€, เชฎเชพเชคเซเชฐ เชธเชฌเชธเซเชŸเซเชฐเชฟเช‚เช—เชจเซ€ เช˜เชŸเชจเชพ เชฎเชพเชŸเซ‡ (-pr-).

เชœเซ‹ เชคเชฎเซ‡ เชšเชพเชฒเซ เช›เซ‹ Linux:

$ sed -i 's|false|true|g' stale-feature-branch-production-configs.yml

เชœเซ‹ เชคเชฎเซ‡ เชšเชพเชฒเซ เช›เซ‹ macOS:

$ sed -i "" 's|false|true|g' stale-feature-branch-production-configs.yml

เชชเซเชฐเซ‹เชœเซ‡เช•เซเชŸ เช‡เชจเซเชธเซเชŸเซ‹เชฒ เช•เชฐเซ€ เชฐเชนเซเชฏเชพ เช›เซ€เช:

$ kubectl apply -f stale-feature-branch-production-configs.yml

เช•เซเชฒเชธเซเชŸเชฐเชฎเชพเช‚ เชธเช‚เชธเชพเชงเชจ เชฆเซ‡เช–เชพเชฏเซเช‚ เช›เซ‡ เชคเซ‡ เชคเชชเชพเชธเซ€ เชฐเชนเซเชฏเซเช‚ เช›เซ‡ StaleFeatureBranch:

$ kubectl api-resources | grep stalefeaturebranches
NAME                 ... APIGROUP                             ... KIND
stalefeaturebranches ... feature-branch.dmytrostriletskyi.com ... StaleFeatureBranch

เช…เชฎเซ‡ เชคเชชเชพเชธเซ€เช เช›เซ€เช เช•เซ‡ เช•เซเชฒเชธเซเชŸเชฐเชฎเชพเช‚ เช‘เชชเชฐเซ‡เชŸเชฐ เชฆเซ‡เช–เชพเชฏเซ‹ เช›เซ‡:

$ kubectl get pods --namespace stale-feature-branch-operator
NAME                                           ... STATUS  ... AGE
stale-feature-branch-operator-6bfbfd4df8-m7sch ... Running ... 38s

เชœเซ‹ เชคเชฎเซ‡ เชคเซ‡เชจเชพ เชฒเซ‹เช—เชจเซ‡ เชœเซเช“, เชคเซ‹ เชคเซ‡ เชธเช‚เชธเชพเชงเชจเซ‹เชจเซ€ เชชเซเชฐเช•เซเชฐเชฟเชฏเชพ เช•เชฐเชตเชพ เชฎเชพเชŸเซ‡ เชคเซˆเชฏเชพเชฐ เช›เซ‡ StaleFeatureBranch:

$ kubectl logs stale-feature-branch-operator-6bfbfd4df8-m7sch -n stale-feature-branch-operator
... "msg":"Operator Version: 0.0.1"}
...
... "msg":"Starting EventSource", ... , "source":"kind source: /, Kind="}
... "msg":"Starting Controller", ...}
... "msg":"Starting workers", ..., "worker count":1}

เช…เชฎเซ‡ เชคเซˆเชฏเชพเชฐ เชธเซเชฅเชพเชชเชฟเชค เช•เชฐเซ€เช เช›เซ€เช fixtures (เชฎเซ‰เชกเชฒเชฟเช‚เช— เช•เซเชฒเชธเซเชŸเชฐ เชธเช‚เชธเชพเชงเชจ เชฎเชพเชŸเซ‡ เชคเซˆเชฏเชพเชฐ เชฐเซ‚เชชเชฐเซ‡เช–เชพเช‚เช•เชจเซ‹) เชธเช‚เชธเชพเชงเชจ เชฎเชพเชŸเซ‡ StaleFeatureBranch:

$ kubectl apply -f https://raw.githubusercontent.com/dmytrostriletskyi/stale-feature-branch-operator/master/fixtures/stale-feature-branch.yml

เชฐเซ‚เชชเชฐเซ‡เช–เชพเช‚เช•เชจเซ‹ เชธเชฌเชธเซเชŸเซเชฐเชฟเช‚เช— เชธเชพเชฅเซ‡ เชจเซ‡เชฎเชธเซเชชเซ‡เชธ เชถเซ‹เชงเชตเชพ เชฎเชพเชŸเซ‡ เชธเซ‚เชšเชตเซ‡ เช›เซ‡ -pr- เชเช•เชตเชพเชฐ เชฎเชพเช‚ 1 ะผะธะฝัƒั‚ัƒ.:

apiVersion: feature-branch.dmytrostriletskyi.com/v1
kind: StaleFeatureBranch
metadata:
  name: stale-feature-branch
spec:
  namespaceSubstring: -pr-
  afterDaysWithoutDeploy: 1 
  checkEveryMinutes: 1

เช“เชชเชฐเซ‡เชŸเชฐเซ‡ เชœเชตเชพเชฌ เช†เชชเซเชฏเซ‹ เช›เซ‡ เช…เชจเซ‡ เชจเซ‡เชฎเชธเซเชชเซ‡เชธ เชคเชชเชพเชธเชตเชพ เชฎเชพเชŸเซ‡ เชคเซˆเชฏเชพเชฐ เช›เซ‡:

$ kubectl logs stale-feature-branch-operator-6bfbfd4df8-m7sch -n stale-feature-branch-operator
... "msg":"Stale feature branch is being processing.","namespaceSubstring":"-pr-","afterDaysWithoutDeploy":1,"checkEveryMinutes":1,"isDebug":"true"}

เชธเซเชฅเชพเชชเชฟเชค เช•เชฐเซ‹ fixtures, เชœเซ‡เชฎเชพเช‚ เชฌเซ‡ เชจเซ‡เชฎเชธเซเชชเซ‡เชธ (project-pr-1, project-pr-2) เช…เชจเซ‡ เชคเซ‡เช“ deployments, services, ingress, เช…เชจเซ‡ เชคเซ‡เชฅเซ€ เชตเชงเซ:

$ kubectl apply -f https://raw.githubusercontent.com/dmytrostriletskyi/stale-feature-branch-operator/master/fixtures/first-feature-branch.yml -f https://raw.githubusercontent.com/dmytrostriletskyi/stale-feature-branch-operator/master/fixtures/second-feature-branch.yml
...
namespace/project-pr-1 created
deployment.apps/project-pr-1 created
service/project-pr-1 created
horizontalpodautoscaler.autoscaling/project-pr-1 created
secret/project-pr-1 created
configmap/project-pr-1 created
ingress.extensions/project-pr-1 created
namespace/project-pr-2 created
deployment.apps/project-pr-2 created
service/project-pr-2 created
horizontalpodautoscaler.autoscaling/project-pr-2 created
secret/project-pr-2 created
configmap/project-pr-2 created
ingress.extensions/project-pr-2 created

เช…เชฎเซ‡ เชคเชชเชพเชธเซ€เช เช›เซ€เช เช•เซ‡ เช‰เชชเชฐเซ‹เช•เซเชค เชคเชฎเชพเชฎ เชธเช‚เชธเชพเชงเชจเซ‹ เชธเชซเชณเชคเชพเชชเซ‚เชฐเซเชตเช• เชฌเชจเชพเชตเชตเชพเชฎเชพเช‚ เช†เชตเซเชฏเชพ เช›เซ‡:

$ kubectl get namespace,pods,deployment,service,horizontalpodautoscaler,configmap,ingress -n project-pr-1 && kubectl get namespace,pods,deployment,service,horizontalpodautoscaler,configmap,ingress -n project-pr-2
...
NAME                              ... READY ... STATUS  ... AGE
pod/project-pr-1-848d5fdff6-rpmzw ... 1/1   ... Running ... 67s

NAME                         ... READY ... AVAILABLE ... AGE
deployment.apps/project-pr-1 ... 1/1   ... 1         ... 67s
...

เช•เชพเชฐเชฃ เช•เซ‡ เช…เชฎเซ‡ เชธเชฎเชพเชตเซ‡เชถ เชฅเชพเชฏ เช›เซ‡ debug, เชจเซ‡เชฎเชธเซเชชเซ‡เชธ project-pr-1 ะธ project-pr-2, เชคเซ‡เชฅเซ€ เชชเซ‡เชฐเชพเชฎเซ€เชŸเชฐเชจเซ‡ เชงเซเชฏเชพเชจเชฎเชพเช‚ เชฒเซ€เชงเชพ เชตเชฟเชจเชพ เช…เชจเซเชฏ เชคเชฎเชพเชฎ เชธเช‚เชธเชพเชงเชจเซ‹ เชคเชฐเชค เชœ เช•เชพเชขเซ€ เชจเชพเช–เชตเชพ เชชเชกเชถเซ‡ afterDaysWithoutDeploy. เช† เช“เชชเชฐเซ‡เชŸเชฐ เชฒเซ‹เช—เชฎเชพเช‚ เชœเซ‹เชˆ เชถเช•เชพเชฏ เช›เซ‡:

$ kubectl logs stale-feature-branch-operator-6bfbfd4df8-m7sch -n stale-feature-branch-operator
... "msg":"Namespace should be deleted due to debug mode is enabled.","namespaceName":"project-pr-1"}
... "msg":"Namespace is being processing.","namespaceName":"project-pr-1","namespaceCreationTimestamp":"2020-06-16 18:43:58 +0300 EEST"}
... "msg":"Namespace has been deleted.","namespaceName":"project-pr-1"}
... "msg":"Namespace should be deleted due to debug mode is enabled.","namespaceName":"project-pr-2"}
... "msg":"Namespace is being processing.","namespaceName":"project-pr-2","namespaceCreationTimestamp":"2020-06-16 18:43:58 +0300 EEST"}
... "msg":"Namespace has been deleted.","namespaceName":"project-pr-2"}

เชœเซ‹ เชคเชฎเซ‡ เชธเช‚เชธเชพเชงเชจเซ‹เชจเซ€ เช‰เชชเชฒเชฌเซเชงเชคเชพ เชคเชชเชพเชธเซ‹, เชคเซ‹ เชคเซ‡ เชธเซเชฅเชฟเชคเชฟเชฎเชพเช‚ เชนเชถเซ‡ Terminating (เช•เชพเชขเซ€ เชจเชพเช–เชตเชพเชจเซ€ เชชเซเชฐเช•เซเชฐเชฟเชฏเชพ) เช…เชฅเชตเชพ เชชเชนเซ‡เชฒเซ‡เชฅเซ€ เช•เชพเชขเซ€ เชจเชพเช–เซ‡เชฒ (เช•เชฎเชพเชจเซเชก เช†เช‰เชŸเชชเซเชŸ เช–เชพเชฒเซ€ เช›เซ‡).

$ kubectl get namespace,pods,deployment,service,horizontalpodautoscaler,configmap,ingress -n project-pr-1 && kubectl get namespace,pods,deployment,service,horizontalpodautoscaler,configmap,ingress -n project-pr-2
...

เชคเชฎเซ‡ เชธเชฐเซเชœเชจ เชชเซเชฐเช•เซเชฐเชฟเชฏเชพเชจเซ‡ เชชเซเชจเชฐเชพเชตเชฐเซเชคเชฟเชค เช•เชฐเซ€ เชถเช•เซ‹ เช›เซ‹ fixtures เช˜เชฃเซ€ เชตเช–เชค เช…เชจเซ‡ เช–เชพเชคเชฐเซ€ เช•เชฐเซ‹ เช•เซ‡ เชคเซ‡เช“ เชเช• เชฎเชฟเชจเชฟเชŸเชฎเชพเช‚ เชฆเซ‚เชฐ เช•เชฐเชตเชพเชฎเชพเช‚ เช†เชตเซ‡ เช›เซ‡.

เชตเชฟเช•เชฒเซเชชเซ‹

เช•เซเชฒเชธเซเชŸเชฐเชฎเชพเช‚ เช•เชพเชฎ เช•เชฐเชคเชพ เช“เชชเชฐเซ‡เชŸเชฐเชจเซ‡ เชฌเชฆเชฒเซ‡ เชถเซเช‚ เช•เชฐเซ€ เชถเช•เชพเชฏ? เชคเซเชฏเชพเช‚ เช˜เชฃเชพ เช…เชญเชฟเช—เชฎเซ‹ เช›เซ‡, เชคเซ‡ เชฌเชงเชพ เช…เชชเซ‚เชฐเซเชฃ เช›เซ‡ (เช…เชจเซ‡ เชคเซ‡เชฎเชจเซ€ เช–เชพเชฎเซ€เช“ เชตเซเชฏเช•เซเชคเชฟเชฒเช•เซเชทเซ€ เช›เซ‡), เช…เชจเซ‡ เชฆเชฐเซ‡เช• เชตเซเชฏเช•เซเชคเชฟ เชชเซ‹เชคเชพเชจเชพ เชฎเชพเชŸเซ‡ เชจเช•เซเช•เซ€ เช•เชฐเซ‡ เช›เซ‡ เช•เซ‡ เช•เซ‹เชˆ เชšเซ‹เช•เซเช•เชธ เชชเซเชฐเซ‹เชœเซ‡เช•เซเชŸ เชฎเชพเชŸเซ‡ เชถเซเชฐเซ‡เชทเซเช  เชถเซเช‚ เช›เซ‡:

  1. เชฎเชพเชธเซเชŸเชฐ เชฌเซเชฐเชพเชจเซเชšเชจเชพ เชธเชคเชค เชเช•เซ€เช•เชฐเชฃ เชฌเชฟเชฒเซเชก เชฆเชฐเชฎเชฟเชฏเชพเชจ เชซเซ€เชšเชฐ เชฌเซเชฐเชพเชจเซเชšเชจเซ‡ เชกเชฟเชฒเซ€เชŸ เช•เชฐเซ‹.

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

  2. เชตเซ‡เชฌเชนเซเช•เซเชธเชจเซ‹ เช‰เชชเชฏเซ‹เช— เช•เชฐเซ€เชจเซ‡ (เช‰เชฆเชพเชนเชฐเชฃ).

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

  3. เชฒเช–เชตเซ เช•เซเชฐเซ‹เชจเชœเซ‹เชฌ เช…เชจเซ‡ เช•เซเชฌเชฐเชจเซ‡เชŸเซเชธ เช•เซเชฒเชธเซเชŸเชฐ เช‰เชฎเซ‡เชฐเซ‹.

    • เชฒเซ‡เช–เชจ เช…เชจเซ‡ เชธเชฎเชฐเซเชฅเชจ เชชเชพเช›เชณ เชธเชฎเชฏ เชชเชธเชพเชฐ เช•เชฐเชตเซ‹.
    • เช“เชชเชฐเซ‡เชŸเชฐ เชชเชนเซ‡เชฒเซ‡เชฅเซ€ เชœ เชธเชฎเชพเชจ เชถเซˆเชฒเซ€เชฎเชพเช‚ เช•เชพเชฎ เช•เชฐเซ‡ เช›เซ‡, เชฆเชธเซเชคเชพเชตเซ‡เชœเซ€เช•เซƒเชค เช…เชจเซ‡ เชธเชชเซ‹เชฐเซเชŸเซ‡เชก เช›เซ‡.

เชฒเซ‡เช– เชชเชฐ เชคเชฎเชพเชฐเซเช‚ เชงเซเชฏเชพเชจ เช†เชชเชตเชพ เชฌเชฆเชฒ เช†เชญเชพเชฐ. Github เชชเชฐ เชชเซเชฐเซ‹เชœเซ‡เช•เซเชŸ เชฎเชพเชŸเซ‡ เชฒเชฟเช‚เช•.

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

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