เจซเฉเจฐเฉ‡เจฎเจตเจฐเจ• เจ…เจคเฉ‡ SDK เจคเฉ‹เจ‚ เจฌเจฟเจจเจพเจ‚ เจชเจพเจˆเจฅเจจ เจตเจฟเฉฑเจš เจ•เฉเจฌเจฐเจจเฉ‡เจŸเจธ เจ“เจชเจฐเฉ‡เจŸเจฐ

เจซเฉเจฐเฉ‡เจฎเจตเจฐเจ• เจ…เจคเฉ‡ SDK เจคเฉ‹เจ‚ เจฌเจฟเจจเจพเจ‚ เจชเจพเจˆเจฅเจจ เจตเจฟเฉฑเจš เจ•เฉเจฌเจฐเจจเฉ‡เจŸเจธ เจ“เจชเจฐเฉ‡เจŸเจฐ

เจ—เฉ‹ เจฆเจพ เจตเจฐเจคเจฎเจพเจจ เจตเจฟเฉฑเจš เจชเฉเจฐเฉ‹เจ—เจฐเจพเจฎเจฟเฉฐเจ— เจญเจพเจธเจผเจพเจตเจพเจ‚ 'เจคเฉ‡ เจเจ•เจพเจงเจฟเจ•เจพเจฐ เจนเฉˆ เจœเฉ‹ เจฒเฉ‹เจ• เจ•เฉเจฌเจฐเจจเฉ‡เจŸเจธ เจฒเจˆ เจฌเจฟเจ†เจจ เจฒเจฟเจ–เจฃ เจฒเจˆ เจšเฉเจฃเจฆเฉ‡ เจนเจจเฅค เจ‡เจธเจฆเฉ‡ เจฌเจพเจนเจฐเจฎเฉเจ–เฉ€ เจ•เจพเจฐเจจ เจนเจจ, เจœเจฟเจตเฉ‡เจ‚ เจ•เจฟ:

  1. เจ—เฉ‹ เจตเจฟเฉฑเจš เจ“เจชเจฐเฉ‡เจŸเจฐเจพเจ‚ เจจเฉ‚เฉฐ เจตเจฟเจ•เจธเจค เจ•เจฐเจจ เจฒเจˆ เจ‡เฉฑเจ• เจธเจผเจ•เจคเฉ€เจธเจผเจพเจฒเฉ€ เจขเจพเจ‚เจšเจพ เจนเฉˆ - เจ“เจชเจฐเฉ‡เจŸเจฐ SDK.
  2. Docker เจ…เจคเฉ‡ Kubernetes เจตเจฐเจ—เฉ€เจ†เจ‚ เจ—เฉ‡เจฎ เจฌเจฆเจฒเจฃ เจตเจพเจฒเฉ€เจ†เจ‚ เจเจชเจฒเฉ€เจ•เฉ‡เจธเจผเจจเจพเจ‚ Go เจตเจฟเฉฑเจš เจฒเจฟเจ–เฉ€เจ†เจ‚ เจ—เจˆเจ†เจ‚ เจนเจจเฅค Go เจตเจฟเฉฑเจš เจ†เจชเจฃเฉ‡ เจ†เจชเจฐเฉ‡เจŸเจฐ เจจเฉ‚เฉฐ เจฒเจฟเจ–เจฃ เจฆเจพ เจฎเจคเจฒเจฌ เจนเฉˆ เจ•เจฟ เจˆเจ•เฉ‹เจธเจฟเจธเจŸเจฎ เจจเจพเจฒ เจ‰เจนเฉ€ เจญเจพเจธเจผเจพ เจฌเฉ‹เจฒเจฃเฉ€เฅค
  3. เจ—เฉ‹ เจเจชเจฒเฉ€เจ•เฉ‡เจธเจผเจจเจพเจ‚ เจฆเจพ เจ‰เฉฑเจš เจชเฉเจฐเจฆเจฐเจธเจผเจจ เจ…เจคเฉ‡ เจฌเจพเจ•เจธ เจฆเฉ‡ เจฌเจพเจนเจฐ เจ‡เจ•เจธเจพเจฐเจคเจพ เจจเจพเจฒ เจ•เฉฐเจฎ เจ•เจฐเจจ เจฒเจˆ เจธเจงเจพเจฐเจจ เจธเจพเจงเจจเฅค

NB: เจคเจฐเฉ€เจ•เฉ‡ เจจเจพเจฒ, เจ—เฉ‹ เจตเจฟเฉฑเจš เจ†เจชเจฃเจพ เจฌเจฟเจ†เจจ เจ•เจฟเจตเฉ‡เจ‚ เจฒเจฟเจ–เจฃเจพ เจนเฉˆ, เจ…เจธเฉ€เจ‚ เจชเจนเจฟเจฒเจพเจ‚ เจนเฉ€ เจฆเฉฑเจธเจฟเจ† เจ—เจฟเจ† เจนเฉˆ เจตเจฟเจฆเฉ‡เจธเจผเฉ€ เจฒเฉ‡เจ–เจ•เจพเจ‚ เจฆเฉเจ†เจฐเจพ เจธเจพเจกเฉ‡ เจ…เจจเฉเจตเจพเจฆเจพเจ‚ เจตเจฟเฉฑเจšเฉ‹เจ‚ เจ‡เฉฑเจ• เจตเจฟเฉฑเจšเฅค

เจชเจฐ เจ‰เจฆเฉ‹เจ‚ เจ•เฉ€ เจœเฉ‡ เจคเฉเจนเจพเจจเฉ‚เฉฐ เจธเจฎเฉ‡เจ‚ เจฆเฉ€ เจ˜เจพเจŸ เจœเจพเจ‚, เจธเจฟเฉฑเจงเฉ‡ เจธเจผเจฌเจฆเจพเจ‚ เจตเจฟเจš, เจชเฉเจฐเฉ‡เจฐเจฃเจพ เจฆเฉเจ†เจฐเจพ เจธเจฟเฉฑเจ–เจฃ เจคเฉ‹เจ‚ เจฐเฉ‹เจ•เจฟเจ† เจœเจพเจ‚เจฆเจพ เจนเฉˆ? เจฒเฉ‡เจ– เจ‡เฉฑเจ• เจ‰เจฆเจพเจนเจฐเจฃ เจชเฉเจฐเจฆเจพเจจ เจ•เจฐเจฆเจพ เจนเฉˆ เจ•เจฟ เจคเฉเจธเฉ€เจ‚ เจธเจญ เจคเฉ‹เจ‚ เจชเฉเจฐเจธเจฟเฉฑเจง เจญเจพเจธเจผเจพเจตเจพเจ‚ เจตเจฟเฉฑเจšเฉ‹เจ‚ เจ‡เฉฑเจ• เจฆเฉ€ เจตเจฐเจคเฉ‹เจ‚ เจ•เจฐเจ•เฉ‡ เจ‡เฉฑเจ• เจตเจงเฉ€เจ† เจฌเจฟเจ†เจจ เจ•เจฟเจตเฉ‡เจ‚ เจฒเจฟเจ– เจธเจ•เจฆเฉ‡ เจนเฉ‹ เจœเฉ‹ เจฒเจ—เจญเจ— เจนเจฐ DevOps เจ‡เฉฐเจœเฉ€เจจเฉ€เจ…เจฐ เจœเจพเจฃเจฆเจพ เจนเฉˆ - เจชเจพเจˆเจฅเจจ.

เจฎเจฟเจฒเฉ‹: เจ•เจพเจชเฉ€เจฐ - เจ•เจพเจชเฉ€ เจ†เจชเจฐเฉ‡เจŸเจฐ!

เจ‡เฉฑเจ• เจ‰เจฆเจพเจนเจฐเจจ เจฆเฉ‡ เจคเฉŒเจฐ 'เจคเฉ‡, เจ‡เฉฑเจ• เจธเจงเจพเจฐเจจ เจธเจŸเฉ‡เจŸเจฎเฉˆเจ‚เจŸ เจตเจฟเจ•เจธเจฟเจค เจ•เจฐเจจ 'เจคเฉ‡ เจตเจฟเจšเจพเจฐ เจ•เจฐเฉ‹ เจœเฉ‹ เจ‡เฉฑเจ• ConfigMap เจฆเฉ€ เจจเจ•เจฒ เจ•เจฐเจจ เจฒเจˆ เจคเจฟเจ†เจฐ เจ•เฉ€เจคเจพ เจ—เจฟเจ† เจนเฉˆ เจœเจพเจ‚ เจคเจพเจ‚ เจœเจฆเฉ‹เจ‚ เจ‡เฉฑเจ• เจจเจตเจพเจ‚ เจจเฉ‡เจฎเจธเจชเฉ‡เจธ เจฆเจฟเจ–เจพเจˆ เจฆเจฟเฉฐเจฆเจพ เจนเฉˆ เจœเจพเจ‚ เจœเจฆเฉ‹เจ‚ เจฆเฉ‹ เจธเฉฐเจธเจฅเจพเจตเจพเจ‚ เจตเจฟเฉฑเจšเฉ‹เจ‚ เจ‡เฉฑเจ• เจฌเจฆเจฒเจฆเจพ เจนเฉˆ: ConfigMap เจ…เจคเฉ‡ เจธเฉ€เจ•เจฐเฉ‡เจŸเฅค เจตเจฟเจนเจพเจฐเจ• เจฆเฉเจฐเจฟเจธเจผเจŸเฉ€เจ•เฉ‹เจฃ เจคเฉ‹เจ‚, เจ“เจชเจฐเฉ‡เจŸเจฐ เจเจชเจฒเฉ€เจ•เฉ‡เจธเจผเจจ เจ•เฉŒเจ‚เจซเจฟเจ—เจฐเฉ‡เจธเจผเจจเจพเจ‚ (เจ•เจจเจซเจฟเจ—เจฎเฉˆเจช เจจเฉ‚เฉฐ เจ…เฉฑเจชเจกเฉ‡เจŸ เจ•เจฐเจ•เฉ‡) เจฆเฉ‡ เจฌเจฒเจ• เจ…เฉฑเจชเจกเฉ‡เจŸ เจ•เจฐเจจ เจœเจพเจ‚ เจ—เฉเจชเจค เจกเฉ‡เจŸเจพ เจจเฉ‚เฉฐ เจ…เฉฑเจชเจกเฉ‡เจŸ เจ•เจฐเจจ เจฒเจˆ เจฒเจพเจญเจฆเจพเจ‡เจ• เจนเฉ‹ เจธเจ•เจฆเจพ เจนเฉˆ - เจ‰เจฆเจพเจนเจฐเจจ เจฒเจˆ, เจกเฉŒเจ•เจฐ เจฐเจœเจฟเจธเจŸเจฐเฉ€ เจจเจพเจฒ เจ•เฉฐเจฎ เจ•เจฐเจจ เจฒเจˆ เจ•เฉเฉฐเจœเฉ€เจ†เจ‚ (เจœเจฆเฉ‹เจ‚ เจจเฉ‡เจฎเจธเจชเฉ‡เจธ เจตเจฟเฉฑเจš เจธเฉ€เจ•เจฐเฉ‡เจŸ เจœเฉ‹เฉœเจฆเฉ‡ เจนเฉ‹เจ)เฅค

เจ…เจคเฉ‡ เจ‡เจธ เจคเจฐเฉเจนเจพเจ‚, เจ‡เฉฑเจ• เจšเฉฐเจ—เฉ‡ เจ†เจชเจฐเฉ‡เจŸเจฐ เจ•เฉ‹เจฒ เจ•เฉ€ เจนเฉ‹เจฃเจพ เจšเจพเจนเฉ€เจฆเจพ เจนเฉˆ:

  1. เจฆเฉ€ เจตเจฐเจคเฉ‹เจ‚ เจ•เจฐเจ•เฉ‡ เจ†เจชเจฐเฉ‡เจŸเจฐ เจจเจพเจฒ เจ—เฉฑเจฒเจฌเจพเจค เจ•เฉ€เจคเฉ€ เจœเจพเจ‚เจฆเฉ€ เจนเฉˆ เจ•เจธเจŸเจฎ เจธเจฐเฉ‹เจค เจชเจฐเจฟเจญเจพเจธเจผเจพเจตเจพเจ‚ (เจ‡เจธ เจคเฉ‹เจ‚ เจฌเจพเจ…เจฆ CRD เจ•เจฟเจนเจพ เจœเจพเจ‚เจฆเจพ เจนเฉˆ)เฅค
  2. เจ†เจชเจฐเฉ‡เจŸเจฐ เจจเฉ‚เฉฐ เจธเฉฐเจฐเจšเจฟเจค เจ•เฉ€เจคเจพ เจœเจพ เจธเจ•เจฆเจพ เจนเฉˆเฅค เจ…เจœเจฟเจนเจพ เจ•เจฐเจจ เจฒเจˆ, เจ…เจธเฉ€เจ‚ เจ•เจฎเจพเจ‚เจก เจฒเจพเจˆเจจ เจซเจฒเฉˆเจ— เจ…เจคเฉ‡ เจตเจพเจคเจพเจตเจฐเจฃ เจตเฉ‡เจฐเฉ€เจเจฌเจฒ เจฆเฉ€ เจตเจฐเจคเฉ‹เจ‚ เจ•เจฐเจพเจ‚เจ—เฉ‡เฅค
  3. เจกเฉŒเจ•เจฐ เจ•เฉฐเจŸเฉ‡เจจเจฐ เจ…เจคเฉ‡ เจนเฉˆเจฒเจฎ เจšเจพเจฐเจŸ เจฆเจพ เจฌเจฟเจฒเจก เจกเจฟเจœเจผเจพเจ‡เจจ เจ•เฉ€เจคเจพ เจ—เจฟเจ† เจนเฉˆ เจคเจพเจ‚ เจœเฉ‹ เจ‰เจชเจญเฉ‹เจ—เจคเจพ เจ†เจธเจพเจจเฉ€ เจจเจพเจฒ (เจธเจผเจพเจฌเจฆเจฟเจ• เจคเฉŒเจฐ 'เจคเฉ‡ เจ‡เฉฑเจ• เจ•เจฎเจพเจ‚เจก เจจเจพเจฒ) เจ†เจชเจฐเฉ‡เจŸเจฐ เจจเฉ‚เฉฐ เจ†เจชเจฃเฉ‡ เจ•เฉเจฌเจฐเจจเฉ‡เจŸเจธ เจ•เจฒเฉฑเจธเจŸเจฐ เจตเจฟเฉฑเจš เจธเจฅเจพเจชเจฟเจค เจ•เจฐ เจธเจ•เจฃเฅค

เจธเฉ€.เจ†เจฐ.เจกเฉ€.

เจ“เจชเจฐเฉ‡เจŸเจฐ เจจเฉ‚เฉฐ เจ‡เจน เจœเจพเจฃเจจ เจฒเจˆ เจ•เจฟ เจ•เจฟเจนเฉœเฉ‡ เจธเจฐเฉ‹เจคเจพเจ‚ เจฆเฉ€ เจญเจพเจฒ เจ•เจฐเจจเฉ€ เจนเฉˆ เจ…เจคเฉ‡ เจ•เจฟเฉฑเจฅเฉ‡ เจฆเฉ‡เจ–เจฃเจพ เจนเฉˆ, เจธเจพเจจเฉ‚เฉฐ เจ‰เจธเจฆเฉ‡ เจฒเจˆ เจ‡เฉฑเจ• เจจเจฟเจฏเจฎ เจจเจฟเจฐเจงเจพเจฐเจค เจ•เจฐเจจ เจฆเฉ€ เจฒเฉ‹เฉœ เจนเฉˆเฅค เจนเจฐเฉ‡เจ• เจจเจฟเจฏเจฎ เจจเฉ‚เฉฐ เจ‡เฉฑเจ• เจธเจฟเฉฐเจ—เจฒ CRD เจตเจธเจคเฉ‚ เจตเจœเฉ‹เจ‚ เจฆเจฐเจธเจพเจ‡เจ† เจœเจพเจตเฉ‡เจ—เจพเฅค เจ‡เจธ CRD เจตเจฟเฉฑเจš เจ•เจฟเจนเฉœเฉ‡ เจ–เฉ‡เจคเจฐ เจนเฉ‹เจฃเฉ‡ เจšเจพเจนเฉ€เจฆเฉ‡ เจนเจจ?

  1. เจธเจฐเฉ‹เจค เจฆเฉ€ เจ•เจฟเจธเจฎ, เจœเจฟเจธ เจฆเฉ€ เจ…เจธเฉ€เจ‚ เจ–เฉ‹เจœ เจ•เจฐเจพเจ‚เจ—เฉ‡ (ConfigMap เจœเจพเจ‚ เจธเฉ€เจ•เจฐเฉ‡เจŸ)เฅค
  2. เจจเจพเจฎ-เจธเจฅเจพเจจเจพเจ‚ เจฆเฉ€ เจธเฉ‚เจšเฉ€, เจœเจฟเจธ เจตเจฟเฉฑเจš เจธเจฐเฉ‹เจค เจธเจฅเจฟเจค เจนเฉ‹เจฃเฉ‡ เจšเจพเจนเฉ€เจฆเฉ‡ เจนเจจเฅค
  3. เจšเฉ‹เจฃเจ•เจพเจฐ, เจœเจฟเจธ เจฆเฉเจ†เจฐเจพ เจ…เจธเฉ€เจ‚ เจจเฉ‡เจฎเจธเจชเฉ‡เจธ เจตเจฟเฉฑเจš เจธเจฐเฉ‹เจคเจพเจ‚ เจฆเฉ€ เจ–เฉ‹เจœ เจ•เจฐเจพเจ‚เจ—เฉ‡เฅค

เจ†เจ“ CRD เจฆเจพ เจตเจฐเจฃเจจ เจ•เจฐเฉ€เจ:

apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: copyrator.flant.com
spec:
  group: flant.com
  versions:
  - name: v1
    served: true
    storage: true
  scope: Namespaced
  names:
    plural: copyrators
    singular: copyrator
    kind: CopyratorRule
    shortNames:
    - copyr
  validation:
    openAPIV3Schema:
      type: object
      properties:
        ruleType:
          type: string
        namespaces:
          type: array
          items:
            type: string
        selector:
          type: string

เจ…เจคเฉ‡ เจ…เจธเฉ€เจ‚ เจ‡เจธเจจเฉ‚เฉฐ เจคเฉเจฐเฉฐเจค เจฌเจฃเจพเจตเจพเจ‚เจ—เฉ‡ เจธเจงเจพเจฐเจจ เจจเจฟเจฏเจฎ - เจจเจพเจฎ เจฆเฉ‡ เจจเจพเจฒ เจจเฉ‡เจฎเจธเจชเฉ‡เจธ เจตเจฟเฉฑเจš เจ–เฉ‹เจœ เจ•เจฐเจจ เจฒเจˆ default เจตเจฐเจ—เฉ‡ เจฒเฉ‡เจฌเจฒ เจฆเฉ‡ เจจเจพเจฒ เจธเจพเจฐเฉ‡ ConfigMap copyrator: "true":

apiVersion: flant.com/v1
kind: CopyratorRule
metadata:
  name: main-rule
  labels:
    module: copyrator
ruleType: configmap
selector:
  copyrator: "true"
namespace: default

เจคเจฟเจ†เจฐ! เจนเฉเจฃ เจธเจพเจจเฉ‚เฉฐ เจ•เจฟเจธเฉ‡ เจคเจฐเฉเจนเจพเจ‚ เจ†เจชเจฃเฉ‡ เจจเจฟเจฏเจฎ เจฌเจพเจฐเฉ‡ เจœเจพเจฃเจ•เจพเจฐเฉ€ เจฒเฉˆเจฃ เจฆเฉ€ เจฒเฉ‹เฉœ เจนเฉˆเฅค เจฎเฉˆเจจเฉ‚เฉฐ เจคเฉเจฐเฉฐเจค เจ‡เฉฑเจ• เจฐเจฟเจœเจผเจฐเจตเฉ‡เจธเจผเจจ เจ•เจฐเจจ เจฆเจฟเจ“ เจ•เจฟ เจ…เจธเฉ€เจ‚ เจ†เจชเจฃเฉ‡ เจ†เจช เจ•เจฒเฉฑเจธเจŸเจฐ API เจธเจฐเจตเจฐ เจจเฉ‚เฉฐ เจฌเฉ‡เจจเจคเฉ€เจ†เจ‚ เจจเจนเฉ€เจ‚ เจฒเจฟเจ–เจพเจ‚เจ—เฉ‡เฅค เจ…เจœเจฟเจนเจพ เจ•เจฐเจจ เจฒเจˆ, เจ…เจธเฉ€เจ‚ เจ‡เฉฑเจ• เจคเจฟเจ†เจฐ เจชเจพเจˆเจฅเจจ เจฒเจพเจ‡เจฌเฉเจฐเฉ‡เจฐเฉ€ เจฆเฉ€ เจตเจฐเจคเฉ‹เจ‚ เจ•เจฐเจพเจ‚เจ—เฉ‡ kubernetes-เจ•เจฒเจพเจ‡เฉฐเจŸ:

import kubernetes
from contextlib import suppress


CRD_GROUP = 'flant.com'
CRD_VERSION = 'v1'
CRD_PLURAL = 'copyrators'


def load_crd(namespace, name):
    client = kubernetes.client.ApiClient()
    custom_api = kubernetes.client.CustomObjectsApi(client)

    with suppress(kubernetes.client.api_client.ApiException):
        crd = custom_api.get_namespaced_custom_object(
            CRD_GROUP,
            CRD_VERSION,
            namespace,
            CRD_PLURAL,
            name,
        )
    return {x: crd[x] for x in ('ruleType', 'selector', 'namespace')}

เจ‡เจธ เจ•เฉ‹เจก เจจเฉ‚เฉฐ เจšเจฒเจพเจ‰เจฃ เจฆเฉ‡ เจจเจคเฉ€เจœเฉ‡ เจตเจœเฉ‹เจ‚, เจธเจพเจจเฉ‚เฉฐ เจนเฉ‡เจ  เจฒเจฟเจ–เจฟเจ†เจ‚ เจฎเจฟเจฒเจฆเจพ เจนเฉˆ:

{'ruleType': 'configmap', 'selector': {'copyrator': 'true'}, 'namespace': ['default']}

เจธเจผเจพเจจเจฆเจพเจฐ: เจ…เจธเฉ€เจ‚ เจ†เจชเจฐเฉ‡เจŸเจฐ เจฒเจˆ เจ‡เฉฑเจ• เจจเจฟเจฏเจฎ เจชเฉเจฐเจพเจชเจค เจ•เจฐเจจ เจตเจฟเฉฑเจš เจ•เจพเจฎเจฏเจพเจฌ เจนเฉ‹เจ. เจ…เจคเฉ‡ เจธเจญ เจคเฉ‹เจ‚ เจฎเจนเฉฑเจคเจตเจชเฉ‚เจฐเจจ, เจ…เจธเฉ€เจ‚ เจ‰เจนเฉ€ เจ•เฉ€เจคเจพ เจœเจฟเจธเจจเฉ‚เฉฐ เจ•เฉเจฌเจฐเจจเฉ‡เจŸเจธ เจคเจฐเฉ€เจ•เจพ เจ•เจฟเจนเจพ เจœเจพเจ‚เจฆเจพ เจนเฉˆเฅค

เจตเจพเจคเจพเจตเจฐเจฃ เจตเฉ‡เจฐเฉ€เจเจฌเจฒ เจœเจพเจ‚ เจซเจฒเฉˆเจ—? เจ…เจธเฉ€เจ‚ เจธเจญ เจ•เฉเจ เจฒเฉˆเจ‚เจฆเฉ‡ เจนเจพเจ‚!

เจ†เจ‰ เจฎเฉเฉฑเจ– เจ†เจชเจฐเฉ‡เจŸเจฐ เจธเฉฐเจฐเจšเจจเจพ เจตเฉฑเจฒ เจตเจงเจฆเฉ‡ เจนเจพเจ‚เฅค เจเจชเจฒเฉ€เจ•เฉ‡เจธเจผเจจเจพเจ‚ เจจเฉ‚เฉฐ เจ•เฉŒเจ‚เจซเจฟเจ—เจฐ เจ•เจฐเจจ เจฒเจˆ เจฆเฉ‹ เจฌเฉเจจเจฟเจ†เจฆเฉ€ เจชเจนเฉเฉฐเจš เจนเจจ:

  1. เจ•เจฎเจพเจ‚เจก เจฒเจพเจˆเจจ เจตเจฟเจ•เจฒเจชเจพเจ‚ เจฆเฉ€ เจตเจฐเจคเฉ‹เจ‚ เจ•เจฐเฉ‹;
  2. เจตเจพเจคเจพเจตเจฐเจฃ เจตเฉ‡เจฐเฉ€เจเจฌเจฒ เจฆเฉ€ เจตเจฐเจคเฉ‹เจ‚ เจ•เจฐเฉ‹เฅค

เจ•เจฎเจพเจ‚เจก เจฒเจพเจˆเจจ เจตเจฟเจ•เจฒเจช เจคเฉเจนเจพเจจเฉ‚เฉฐ เจกเจพเจŸเจพ เจ•เจฟเจธเจฎ เจฆเฉ‡ เจธเจฎเจฐเจฅเจจ เจ…เจคเฉ‡ เจชเฉเจฐเจฎเจพเจฃเจฟเจ•เจคเจพ เจฆเฉ‡ เจจเจพเจฒ เจธเฉˆเจŸเจฟเฉฐเจ—เจพเจ‚ เจจเฉ‚เฉฐ เจตเจงเฉ‡เจฐเฉ‡ เจฒเจšเจ•เจฆเจพเจฐ เจขเฉฐเจ— เจจเจพเจฒ เจชเฉœเฉเจนเจจ เจฆเฉ€ เจ‡เจœเจพเจœเจผเจค เจฆเจฟเฉฐเจฆเฉ‡ เจนเจจเฅค เจชเจพเจˆเจฅเจจ เจฆเฉ€ เจธเจŸเฉˆเจ‚เจกเจฐเจก เจฒเจพเจ‡เจฌเฉเจฐเฉ‡เจฐเฉ€ เจตเจฟเฉฑเจš เจ‡เฉฑเจ• เจฎเฉ‹เจกเฉ€เจŠเจฒ เจนเฉˆ argparser, เจœเฉ‹ เจ…เจธเฉ€เจ‚ เจตเจฐเจคเจพเจ‚เจ—เฉ‡เฅค เจ‡เจธ เจฆเฉ€เจ†เจ‚ เจธเจฎเจฐเฉฑเจฅเจพเจตเจพเจ‚ เจฆเฉ‡ เจตเฉ‡เจฐเจตเฉ‡ เจ…เจคเฉ‡ เจ‰เจฆเจพเจนเจฐเจฃเจพเจ‚ เจตเจฟเฉฑเจš เจ‰เจชเจฒเจฌเจง เจนเจจ เจ…เจงเจฟเจ•เจพเจฐเจค เจฆเจธเจคเจพเจตเฉ‡เจœเจผ.

เจธเจพเจกเฉ‡ เจ•เฉ‡เจธ เจฒเจˆ, เจ‡เจน เจนเฉˆ เจ•เจฟ เจฐเฉ€เจกเจฟเฉฐเจ— เจ•เจฎเจพเจ‚เจก เจฒเจพเจˆเจจ เจซเจฒเฉˆเจ— เจธเจฅเจพเจชเจค เจ•เจฐเจจ เจฆเฉ€ เจ‡เฉฑเจ• เจ‰เจฆเจพเจนเจฐเจฃ เจ‡เจธ เจคเจฐเฉเจนเจพเจ‚ เจฆเจฟเจ–เจพเจˆ เจฆเฉ‡เจตเฉ‡เจ—เฉ€:

   parser = ArgumentParser(
        description='Copyrator - copy operator.',
        prog='copyrator'
    )
    parser.add_argument(
        '--namespace',
        type=str,
        default=getenv('NAMESPACE', 'default'),
        help='Operator Namespace'
    )
    parser.add_argument(
        '--rule-name',
        type=str,
        default=getenv('RULE_NAME', 'main-rule'),
        help='CRD Name'
    )
    args = parser.parse_args()

เจฆเฉ‚เจœเฉ‡ เจชเจพเจธเฉ‡, เจ•เฉเจฌเจฐเจจเฉ‡เจŸเจธ เจตเจฟเฉฑเจš เจตเจพเจคเจพเจตเจฐเจฃ เจตเฉ‡เจฐเฉ€เจเจฌเจฒ เจฆเฉ€ เจตเจฐเจคเฉ‹เจ‚ เจ•เจฐเจฆเฉ‡ เจนเฉ‹เจ, เจคเฉเจธเฉ€เจ‚ เจ•เฉฐเจŸเฉ‡เจจเจฐ เจฆเฉ‡ เจ…เฉฐเจฆเจฐ เจชเฉ‹เจก เจฌเจพเจฐเฉ‡ เจธเฉ‡เจตเจพ เจœเจพเจฃเจ•เจพเจฐเฉ€ เจจเฉ‚เฉฐ เจ†เจธเจพเจจเฉ€ เจจเจพเจฒ เจŸเฉเจฐเจพเจ‚เจธเจซเจฐ เจ•เจฐ เจธเจ•เจฆเฉ‡ เจนเฉ‹เฅค เจ‰เจฆเจพเจนเจฐเจจ เจฒเจˆ, เจ…เจธเฉ€เจ‚ เจจเจพเจฎ-เจธเจชเฉ‡เจธ เจฌเจพเจฐเฉ‡ เจœเจพเจฃเจ•เจพเจฐเฉ€ เจชเฉเจฐเจพเจชเจค เจ•เจฐ เจธเจ•เจฆเฉ‡ เจนเจพเจ‚ เจœเจฟเจธ เจตเจฟเฉฑเจš เจชเฉŒเจก เจนเฉ‡เจ เจพเจ‚ เจฆเจฟเฉฑเจคเฉ‡ เจจเจฟเจฐเจฎเจพเจฃ เจจเจพเจฒ เจšเฉฑเจฒ เจฐเจฟเจนเจพ เจนเฉˆ:

env:
- name: NAMESPACE
  valueFrom:
     fieldRef:
         fieldPath: metadata.namespace 

เจ†เจชเจฐเฉ‡เจŸเจฐ เจคเจฐเจ•

เจ‡เจน เจธเจฎเจเจฃ เจฒเจˆ เจ•เจฟ ConfigMap เจ…เจคเฉ‡ เจธเฉ€เจ•เจฐเฉ‡เจŸ เจจเจพเจฒ เจ•เฉฐเจฎ เจ•เจฐเจจ เจฆเฉ‡ เจขเฉฐเจ—เจพเจ‚ เจจเฉ‚เฉฐ เจ•เจฟเจตเฉ‡เจ‚ เจตเฉฑเจ– เจ•เจฐเจจเจพ เจนเฉˆ, เจ…เจธเฉ€เจ‚ เจตเจฟเจธเจผเฉ‡เจธเจผ เจจเจ•เจธเจผเจฟเจ†เจ‚ เจฆเฉ€ เจตเจฐเจคเฉ‹เจ‚ เจ•เจฐเจพเจ‚เจ—เฉ‡เฅค เจซเจฟเจฐ เจ…เจธเฉ€เจ‚ เจธเจฎเจ เจธเจ•เจฆเฉ‡ เจนเจพเจ‚ เจ•เจฟ เจธเจพเจจเฉ‚เฉฐ เจ†เจฌเจœเฉˆเจ•เจŸ เจจเฉ‚เฉฐ เจŸเจฐเฉˆเจ• เจ•เจฐเจจ เจ…เจคเฉ‡ เจฌเจฃเจพเจ‰เจฃ เจฒเจˆ เจ•เจฟเจนเฉœเฉ‡ เจคเจฐเฉ€เจ•เจฟเจ†เจ‚ เจฆเฉ€ เจฒเฉ‹เฉœ เจนเฉˆ:

LIST_TYPES_MAP = {
    'configmap': 'list_namespaced_config_map',
    'secret': 'list_namespaced_secret',
}

CREATE_TYPES_MAP = {
    'configmap': 'create_namespaced_config_map',
    'secret': 'create_namespaced_secret',
}

เจ…เฉฑเจ—เฉ‡, เจคเฉเจนเจพเจจเฉ‚เฉฐ API เจธเจฐเจตเจฐ เจคเฉ‹เจ‚ เจ‡เจตเฉˆเจ‚เจŸ เจชเฉเจฐเจพเจชเจค เจ•เจฐเจจ เจฆเฉ€ เจฒเฉ‹เฉœ เจนเฉˆเฅค เจ†เจ“ เจ‡เจธเจจเฉ‚เฉฐ เจนเฉ‡เจ  เจฒเจฟเจ–เฉ‡ เจ…เจจเฉเจธเจพเจฐ เจฒเจพเจ—เฉ‚ เจ•เจฐเฉ€เจ:

def handle(specs):
    kubernetes.config.load_incluster_config()
    v1 = kubernetes.client.CoreV1Api()

    # ะŸะพะปัƒั‡ะฐะตะผ ะผะตั‚ะพะด ะดะปั ัะปะตะถะตะฝะธั ะทะฐ ะพะฑัŠะตะบั‚ะฐะผะธ
    method = getattr(v1, LIST_TYPES_MAP[specs['ruleType']])
    func = partial(method, specs['namespace'])

    w = kubernetes.watch.Watch()
    for event in w.stream(func, _request_timeout=60):
        handle_event(v1, specs, event)

เจ‡เจตเฉˆเจ‚เจŸ เจชเฉเจฐเจพเจชเจค เจ•เจฐเจจ เจคเฉ‹เจ‚ เจฌเจพเจ…เจฆ, เจ…เจธเฉ€เจ‚ เจ‡เจธเจจเฉ‚เฉฐ เจชเฉเจฐเฉ‹เจธเฉˆเจธ เจ•เจฐเจจ เจฆเฉ‡ เจฎเฉเฉฑเจ– เจคเจฐเจ• เจตเฉฑเจฒ เจตเจงเจฆเฉ‡ เจนเจพเจ‚:

# ะขะธะฟั‹ ัะพะฑั‹ั‚ะธะน, ะฝะฐ ะบะพั‚ะพั€ั‹ะต ะฑัƒะดะตะผ ั€ะตะฐะณะธั€ะพะฒะฐั‚ัŒ
ALLOWED_EVENT_TYPES = {'ADDED', 'UPDATED'}


def handle_event(v1, specs, event):
    if event['type'] not in ALLOWED_EVENT_TYPES:
        return

    object_ = event['object']
    labels = object_['metadata'].get('labels', {})

    # ะ˜ั‰ะตะผ ัะพะฒะฟะฐะดะตะฝะธั ะฟะพ selector'ัƒ
    for key, value in specs['selector'].items():
        if labels.get(key) != value:
            return
    # ะŸะพะปัƒั‡ะฐะตะผ ะฐะบั‚ะธะฒะฝั‹ะต namespace'ั‹
    namespaces = map(
        lambda x: x.metadata.name,
        filter(
            lambda x: x.status.phase == 'Active',
            v1.list_namespace().items
        )
    )
    for namespace in namespaces:
        # ะžั‡ะธั‰ะฐะตะผ ะผะตั‚ะฐะดะฐะฝะฝั‹ะต, ัƒัั‚ะฐะฝะฐะฒะปะธะฒะฐะตะผ namespace
        object_['metadata'] = {
            'labels': object_['metadata']['labels'],
            'namespace': namespace,
            'name': object_['metadata']['name'],
        }
        # ะ’ั‹ะทั‹ะฒะฐะตะผ ะผะตั‚ะพะด ัะพะทะดะฐะฝะธั/ะพะฑะฝะพะฒะปะตะฝะธั ะพะฑัŠะตะบั‚ะฐ
        methodcaller(
            CREATE_TYPES_MAP[specs['ruleType']],
            namespace,
            object_
        )(v1)

เจฎเฉเฉฑเจ– เจคเจฐเจ• เจคเจฟเจ†เจฐ เจนเฉˆ! เจนเฉเจฃ เจธเจพเจจเฉ‚เฉฐ เจ‡เจน เจธเจญ เจ‡เฉฑเจ• Python เจชเฉˆเจ•เฉ‡เจœ เจตเจฟเฉฑเจš เจชเฉˆเจ• เจ•เจฐเจจ เจฆเฉ€ เจฒเฉ‹เฉœ เจนเฉˆเฅค เจ…เจธเฉ€เจ‚ เจซเจพเจˆเจฒ เจคเจฟเจ†เจฐ เจ•เจฐเจฆเฉ‡ เจนเจพเจ‚ setup.py, เจ‰เฉฑเจฅเฉ‡ เจชเฉเจฐเฉ‹เจœเฉˆเจ•เจŸ เจฌเจพเจฐเฉ‡ เจฎเฉˆเจŸเจพ เจœเจพเจฃเจ•เจพเจฐเฉ€ เจฒเจฟเจ–เฉ‹:

from sys import version_info

from setuptools import find_packages, setup

if version_info[:2] < (3, 5):
    raise RuntimeError(
        'Unsupported python version %s.' % '.'.join(version_info)
    )


_NAME = 'copyrator'
setup(
    name=_NAME,
    version='0.0.1',
    packages=find_packages(),
    classifiers=[
        'Development Status :: 3 - Alpha',
        'Programming Language :: Python',
        'Programming Language :: Python :: 3',
        'Programming Language :: Python :: 3.5',
        'Programming Language :: Python :: 3.6',
        'Programming Language :: Python :: 3.7',
    ],
    author='Flant',
    author_email='[email protected]',
    include_package_data=True,
    install_requires=[
        'kubernetes==9.0.0',
    ],
    entry_points={
        'console_scripts': [
            '{0} = {0}.cli:main'.format(_NAME),
        ]
    }
)

NB: เจชเจพเจˆเจฅเจจ เจฒเจˆ เจ•เฉเจฌเจฐเจจเฉ‡เจŸเจธ เจ•เจฒเจพเจ‡เฉฐเจŸ เจฆเจพ เจ†เจชเจฃเจพ เจธเฉฐเจธเจ•เจฐเจฃ เจนเฉˆเฅค เจ•เจฒเจพเจ‡เฉฐเจŸ เจธเฉฐเจธเจ•เจฐเจฃเจพเจ‚ เจ…เจคเฉ‡ เจ•เฉเจฌเจฐเจจเฉ‡เจŸเจธ เจธเฉฐเจธเจ•เจฐเจฃเจพเจ‚ เจตเจฟเจšเจ•เจพเจฐ เจ…เจจเฉเจ•เฉ‚เจฒเจคเจพ เจฌเจพเจฐเฉ‡ เจตเจงเฉ‡เจฐเฉ‡ เจœเจพเจฃเจ•เจพเจฐเฉ€ เจตเจฟเฉฑเจš เจฒเฉฑเจญเฉ€ เจœเจพ เจธเจ•เจฆเฉ€ เจนเฉˆ เจ…เจจเฉเจ•เฉ‚เจฒเจคเจพ เจฎเฉˆเจŸเฉเจฐเจฟเจ•เจธ.

เจนเฉเจฃ เจธเจพเจกเจพ เจชเฉเจฐเฉ‹เจœเฉˆเจ•เจŸ เจ‡เจธ เจคเจฐเฉเจนเจพเจ‚ เจฆเจฟเจธเจฆเจพ เจนเฉˆ:

copyrator
โ”œโ”€โ”€ copyrator
โ”‚   โ”œโ”€โ”€ cli.py # ะ›ะพะณะธะบะฐ ั€ะฐะฑะพั‚ั‹ ั ะบะพะผะฐะฝะดะฝะพะน ัั‚ั€ะพะบะพะน
โ”‚   โ”œโ”€โ”€ constant.py # ะšะพะฝัั‚ะฐะฝั‚ั‹, ะบะพั‚ะพั€ั‹ะต ะผั‹ ะฟั€ะธะฒะพะดะธะปะธ ะฒั‹ัˆะต
โ”‚   โ”œโ”€โ”€ load_crd.py # ะ›ะพะณะธะบะฐ ะทะฐะณั€ัƒะทะบะธ CRD
โ”‚   โ””โ”€โ”€ operator.py # ะžัะฝะพะฒะฝะฐั ะปะพะณะธะบะฐ ั€ะฐะฑะพั‚ั‹ ะพะฟะตั€ะฐั‚ะพั€ะฐ
โ””โ”€โ”€ setup.py # ะžั„ะพั€ะผะปะตะฝะธะต ะฟะฐะบะตั‚ะฐ

เจกเฉŒเจ•เจฐ เจ…เจคเฉ‡ เจนเฉˆเจฒเจฎ

เจกเฉŒเจ•เจฐเจซเจพเจˆเจฒ เจฌเจนเฉเจค เจนเฉ€ เจธเจงเจพเจฐเจจ เจนเฉ‹เจตเฉ‡เจ—เฉ€: เจฌเฉ‡เจธ เจชเจพเจˆเจฅเจจ-เจเจฒเจชเจพเจˆเจจ เจšเจฟเฉฑเจคเจฐ เจฒเจ“ เจ…เจคเฉ‡ เจธเจพเจกเฉ‡ เจชเฉˆเจ•เฉ‡เจœ เจจเฉ‚เฉฐ เจธเจฅเจพเจชเจฟเจค เจ•เจฐเฉ‹เฅค เจ†เจ“ เจ‡เจธ เจฆเฉ‡ เจ…เจจเฉเจ•เฉ‚เจฒเจจ เจจเฉ‚เฉฐ เจฌเจฟเจนเจคเจฐ เจธเจฎเฉ‡เจ‚ เจคเฉฑเจ• เจฎเฉเจฒเจคเจตเฉ€ เจ•เจฐเฉ€เจ:

FROM python:3.7.3-alpine3.9

ADD . /app

RUN pip3 install /app

ENTRYPOINT ["copyrator"]

เจ†เจชเจฐเฉ‡เจŸเจฐ เจฒเจˆ เจคเฉˆเจจเจพเจคเฉ€ เจตเฉ€ เจฌเจนเฉเจค เจธเจงเจพเจฐเจจ เจนเฉˆ:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Chart.Name }}
spec:
  selector:
    matchLabels:
      name: {{ .Chart.Name }}
  template:
    metadata:
      labels:
        name: {{ .Chart.Name }}
    spec:
      containers:
      - name: {{ .Chart.Name }}
        image: privaterepo.yourcompany.com/copyrator:latest
        imagePullPolicy: Always
        args: ["--rule-type", "main-rule"]
        env:
        - name: NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
      serviceAccountName: {{ .Chart.Name }}-acc

เจ…เฉฐเจค เจตเจฟเฉฑเจš, เจคเฉเจนเจพเจจเฉ‚เฉฐ เจฒเฉ‹เฉœเฉ€เจ‚เจฆเฉ‡ เจ…เจงเจฟเจ•เจพเจฐเจพเจ‚ เจฆเฉ‡ เจจเจพเจฒ เจ“เจชเจฐเฉ‡เจŸเจฐ เจฒเจˆ เจ‡เฉฑเจ• เจขเฉเจ•เจตเฉ€เจ‚ เจญเฉ‚เจฎเจฟเจ•เจพ เจฌเจฃเจพเจ‰เจฃ เจฆเฉ€ เจฒเฉ‹เฉœ เจนเฉˆ:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: {{ .Chart.Name }}-acc

---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: {{ .Chart.Name }}
rules:
  - apiGroups: [""]
    resources: ["namespaces"]
    verbs: ["get", "watch", "list"]
  - apiGroups: [""]
    resources: ["secrets", "configmaps"]
    verbs: ["*"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: {{ .Chart.Name }}
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: {{ .Chart.Name }}
subjects:
- kind: ServiceAccount
  name: {{ .Chart.Name }}

เจจเจคเฉ€เจœเจพ

เจ‡เจธ เจคเจฐเฉเจนเจพเจ‚, เจฌเจฟเจจเจพเจ‚ เจ•เจฟเจธเฉ‡ เจกเจฐ, เจฌเจฆเจจเจพเจฎเฉ€, เจœเจพเจ‚ เจ—เฉ‹ เจธเจฟเฉฑเจ–เจฃ เจฆเฉ‡, เจ…เจธเฉ€เจ‚ เจชเจพเจˆเจฅเจจ เจตเจฟเฉฑเจš เจ•เฉเจฌเจฐเจจเฉ‡เจŸเจธ เจฒเจˆ เจ†เจชเจฃเจพ เจ†เจชเจฐเฉ‡เจŸเจฐ เจฌเจฃเจพเจ‰เจฃ เจฆเฉ‡ เจฏเฉ‹เจ— เจนเฉ‹ เจ—เจเฅค เจฌเฉ‡เจธเจผเฉฑเจ•, เจ‡เจธ เจตเจฟเฉฑเจš เจ…เจœเฉ‡ เจตเฉ€ เจตเจงเจฃ เจฒเจˆ เจฅเจพเจ‚ เจนเฉˆ: เจญเจตเจฟเฉฑเจ– เจตเจฟเฉฑเจš เจ‡เจน เจ•เจˆ เจจเจฟเจฏเจฎเจพเจ‚ เจฆเฉ€ เจชเฉเจฐเจ•เจฟเจฐเจฟเจ† เจ•เจฐเจจ เจฆเฉ‡ เจฏเฉ‹เจ— เจนเฉ‹เจตเฉ‡เจ—เจพ, เจฎเจฒเจŸเฉ€เจชเจฒ เจฅเจฐเจฟเฉฑเจกเจพเจ‚ เจตเจฟเฉฑเจš เจ•เฉฐเจฎ เจ•เจฐเฉ‡เจ—เจพ, เจธเฉเจคเฉฐเจคเจฐ เจคเฉŒเจฐ 'เจคเฉ‡ เจ‡เจธเจฆเฉ‡ CRDs เจตเจฟเฉฑเจš เจคเจฌเจฆเฉ€เจฒเฉ€เจ†เจ‚ เจฆเฉ€ เจจเจฟเจ—เจฐเจพเจจเฉ€ เจ•เจฐเฉ‡เจ—เจพ...

เจคเฉเจนเจพเจจเฉ‚เฉฐ เจ•เฉ‹เจก 'เจคเฉ‡ เจจเฉ‡เฉœเจฟเจ“เจ‚ เจจเจœเจผเจฐ เจฆเฉ‡เจฃ เจฒเจˆ, เจ…เจธเฉ€เจ‚ เจ‡เจธเจจเฉ‚เฉฐ เจชเจพ เจฆเจฟเฉฑเจคเจพ เจนเฉˆ เจœเจจเจคเจ• เจญเฉฐเจกเจพเจฐ. เจœเฉ‡ เจคเฉเจธเฉ€เจ‚ เจชเจพเจ‡เจฅเจจ เจฆเฉ€ เจตเจฐเจคเฉ‹เจ‚ เจ•เจฐเจ•เฉ‡ เจฒเจพเจ—เฉ‚ เจ•เฉ€เจคเฉ‡ เจนเฉ‹เจฐ เจ—เฉฐเจญเฉ€เจฐ เจ“เจชเจฐเฉ‡เจŸเจฐเจพเจ‚ เจฆเฉ€เจ†เจ‚ เจ‰เจฆเจพเจนเจฐเจฃเจพเจ‚ เจšเจพเจนเฉเฉฐเจฆเฉ‡ เจนเฉ‹, เจคเจพเจ‚ เจคเฉเจธเฉ€เจ‚ เจฎเฉ‹เจ‚เจ—เฉŒเจกเจฌเฉ€ (ะฟะตั€ะฒั‹ะน ะธ เจฆเฉ‚เจœเจพ).

PS เจ…เจคเฉ‡ เจœเฉ‡เจ•เจฐ เจคเฉเจธเฉ€เจ‚ เจ•เฉเจฌเจฐเจจเฉ‡เจŸเจธ เจ‡เจตเฉˆเจ‚เจŸเจธ เจจเจพเจฒ เจจเจœเจฟเฉฑเจ เจฃ เจฒเจˆ เจฌเจนเฉเจค เจ†เจฒเจธเฉ€ เจนเฉ‹ เจœเจพเจ‚ เจคเฉเจธเฉ€เจ‚ เจฌเฉˆเจธเจผ เจฆเฉ€ เจตเจฐเจคเฉ‹เจ‚ เจ•เจฐเจจ เจฆเฉ‡ เจœเจผเจฟเจ†เจฆเจพ เจ†เจฆเฉ€ เจนเฉ‹, เจคเจพเจ‚ เจธเจพเจกเฉ‡ เจธเจนเจฟเจฏเฉ‹เจ—เฉ€เจ†เจ‚ เจจเฉ‡ เจซเจพเจฐเจฎ เจตเจฟเฉฑเจš เจ‡เฉฑเจ• เจคเจฟเจ†เจฐ เจนเฉฑเจฒ เจคเจฟเจ†เจฐ เจ•เฉ€เจคเจพ เจนเฉˆ เจธเจผเฉˆเฉฑเจฒ-เจ†เจชเจฐเฉ‡เจŸเจฐ (เจ…เจธเฉ€เจ‚ เจเจฒเจพเจจ เจ•เฉ€เจคเจพ เจ‡เจน เจ…เจชเฉเจฐเฉˆเจฒ เจตเจฟเฉฑเจš).

Pps

เจธเจพเจกเฉ‡ เจฌเจฒเฉŒเจ— 'เจคเฉ‡ เจตเฉ€ เจชเฉœเฉเจนเฉ‹:

เจธเจฐเฉ‹เจค: www.habr.com

เจ‡เฉฑเจ• เจŸเจฟเฉฑเจชเจฃเฉ€ เจœเฉ‹เฉœเฉ‹