ํ”„๋ ˆ์ž„์›Œํฌ์™€ SDK๊ฐ€ ์—†๋Š” Python์˜ Kubernetes Operator

ํ”„๋ ˆ์ž„์›Œํฌ์™€ SDK๊ฐ€ ์—†๋Š” Python์˜ Kubernetes Operator

Go๋Š” ํ˜„์žฌ ์‚ฌ๋žŒ๋“ค์ด Kubernetes์šฉ ๋ช…๋ น๋ฌธ์„ ์ž‘์„ฑํ•˜๊ธฐ ์œ„ํ•ด ์„ ํƒํ•˜๋Š” ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด๋ฅผ ๋…์ ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด์— ๋Œ€ํ•œ ๊ฐ๊ด€์ ์ธ ์ด์œ ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  1. Go์—๋Š” ์—ฐ์‚ฐ์ž ๊ฐœ๋ฐœ์„ ์œ„ํ•œ ๊ฐ•๋ ฅํ•œ ํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฐ์‚ฐ์ž SDK.
  2. Docker ๋ฐ Kubernetes์™€ ๊ฐ™์€ ํŒ๋„๋ฅผ ๋ฐ”๊พธ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ Go๋กœ ์ž‘์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. Go์—์„œ ์—ฐ์‚ฐ์ž๋ฅผ ์ž‘์„ฑํ•œ๋‹ค๋Š” ๊ฒƒ์€ ์ƒํƒœ๊ณ„์™€ ๋™์ผํ•œ ์–ธ์–ด๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋Š” ์˜๋ฏธ์ž…๋‹ˆ๋‹ค.
  3. ์ฆ‰์‹œ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋™์‹œ ์ž‘์—…์„ ์œ„ํ•œ ๊ณ ์„ฑ๋Šฅ Go ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๊ณผ ๊ฐ„๋‹จํ•œ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค.

NB: ๊ทธ๋Ÿฐ๋ฐ Go์—์„œ ์ž์‹ ๋งŒ์˜ ๋ฌธ์žฅ์„ ์ž‘์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ๋ ค๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค. ์ด๋ฏธ ์„ค๋ช…๋จ ์™ธ๊ตญ ์ž‘๊ฐ€๋“ค์˜ ๋ฒˆ์—ญ ์ค‘ ํ•˜๋‚˜์—์„œ.

ํ•˜์ง€๋งŒ ์‹œ๊ฐ„์ด ๋ถ€์กฑํ•˜๊ฑฐ๋‚˜ ๊ฐ„๋‹จํžˆ ๋งํ•ด์„œ ๋™๊ธฐ๊ฐ€ ๋ถ€์กฑํ•˜์—ฌ ๋ฐ”๋‘‘์„ ๋ฐฐ์šฐ์ง€ ๋ชปํ•œ๋‹ค๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ์š”? ์ด ๊ธฐ์‚ฌ์—์„œ๋Š” ๊ฑฐ์˜ ๋ชจ๋“  DevOps ์—”์ง€๋‹ˆ์–ด๊ฐ€ ์•Œ๊ณ  ์žˆ๋Š” ๊ฐ€์žฅ ๋„๋ฆฌ ์‚ฌ์šฉ๋˜๋Š” ์–ธ์–ด ์ค‘ ํ•˜๋‚˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ข‹์€ ๋ฌธ์žฅ์„ ์ž‘์„ฑํ•˜๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•œ ์˜ˆ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. Python.

๋งŒ๋‚˜๋ณด์„ธ์š”: ๋ณต์‚ฌ๊ธฐ - ๋ณต์‚ฌ๊ธฐ ์šด์˜์ž!

์˜ˆ๋ฅผ ๋“ค์–ด, ์ƒˆ ๋„ค์ž„์ŠคํŽ˜์ด์Šค๊ฐ€ ๋‚˜ํƒ€๋‚  ๋•Œ ๋˜๋Š” ๋‘ ์—”ํ„ฐํ‹ฐ(ConfigMap ๋ฐ Secret) ์ค‘ ํ•˜๋‚˜๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ ConfigMap์„ ๋ณต์‚ฌํ•˜๋„๋ก ์„ค๊ณ„๋œ ๊ฐ„๋‹จํ•œ ๋ฌธ์„ ๊ฐœ๋ฐœํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด ๋ณด์„ธ์š”. ์‹ค์šฉ์ ์ธ ๊ด€์ ์—์„œ ์—ฐ์‚ฐ์ž๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ตฌ์„ฑ์„ ๋Œ€๋Ÿ‰์œผ๋กœ ์—…๋ฐ์ดํŠธํ•˜๊ฑฐ๋‚˜(ConfigMap ์—…๋ฐ์ดํŠธ๋ฅผ ํ†ตํ•ด) ๋น„๋ฐ€ ๋ฐ์ดํ„ฐ(์˜ˆ: Docker ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ์ž‘์—…์„ ์œ„ํ•œ ํ‚ค(๋„ค์ž„์ŠคํŽ˜์ด์Šค์— ๋น„๋ฐ€์„ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒฝ์šฐ))๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๋ฐ ์œ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ, ์ข‹์€ ์šด์˜์ž๋Š” ๋ฌด์—‡์„ ๊ฐ€์ ธ์•ผํ•ฉ๋‹ˆ๊นŒ?:

  1. ์šด์˜์ž์™€์˜ ์ƒํ˜ธ ์ž‘์šฉ์€ ๋‹ค์Œ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ˆ˜ํ–‰๋ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž ์ •์˜ ๋ฆฌ์†Œ์Šค ์ •์˜ (์ดํ•˜ CRD๋ผ ์นญํ•จ)
  2. ์šด์˜์ž๋ฅผ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด ๋ช…๋ น์ค„ ํ”Œ๋ž˜๊ทธ์™€ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  3. Docker ์ปจํ…Œ์ด๋„ˆ ๋ฐ Helm ์ฐจํŠธ์˜ ๋นŒ๋“œ๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์‰ฝ๊ฒŒ(๋ง ๊ทธ๋Œ€๋กœ ํ•˜๋‚˜์˜ ๋ช…๋ น์„ ์‚ฌ์šฉํ•˜์—ฌ) ์—ฐ์‚ฐ์ž๋ฅผ Kubernetes ํด๋Ÿฌ์Šคํ„ฐ์— ์„ค์น˜ํ•  ์ˆ˜ ์žˆ๋„๋ก ์„ค๊ณ„๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

CRD

์šด์˜์ž๊ฐ€ ์–ด๋–ค ๋ฆฌ์†Œ์Šค๋ฅผ ์ฐพ์•„์•ผ ํ•˜๋Š”์ง€, ์–ด๋””๋ฅผ ์ฐพ์•„์•ผ ํ•˜๋Š”์ง€ ์•Œ๊ธฐ ์œ„ํ•ด์„œ๋Š” ๊ทธ์— ๋Œ€ํ•œ ๊ทœ์น™์„ ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ ๊ทœ์น™์€ ๋‹จ์ผ CRD ๊ฐœ์ฒด๋กœ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค. ์ด CRD์—๋Š” ์–ด๋–ค ํ•„๋“œ๊ฐ€ ์žˆ์–ด์•ผ ํ•ฉ๋‹ˆ๊นŒ?

  1. ๋ฆฌ์†Œ์Šค ์œ ํ˜•, ์šฐ๋ฆฌ๊ฐ€ ์ฐพ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค (ConfigMap ๋˜๋Š” Secret).
  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 ์„œ๋ฒ„์— ์ง์ ‘ ์š”์ฒญ์„ ์ž‘์„ฑํ•˜์ง€ ์•Š์„ ๊ฒƒ์ž„์„ ์ฆ‰์‹œ ์˜ˆ์•ฝํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด ์ด๋ฏธ ๋งŒ๋“ค์–ด์ง„ Python ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค ํด๋ผ์ด์–ธํŠธ:

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']}

ํ›Œ๋ฅญํ•ฉ๋‹ˆ๋‹ค. ์šฐ๋ฆฌ๋Š” ์šด์˜์ž์— ๋Œ€ํ•œ ๊ทœ์น™์„ ์–ป์—ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๊ฒƒ์€ ์šฐ๋ฆฌ๊ฐ€ Kubernetes ๋ฐฉ์‹์ด๋ผ๋Š” ๊ฒƒ์„ ์ˆ˜ํ–‰ํ–ˆ๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๋˜๋Š” ํ”Œ๋ž˜๊ทธ? ์šฐ๋ฆฌ๋Š” ๋ชจ๋“  ๊ฒƒ์„ ๊ฐ€์ ธ๊ฐ‘๋‹ˆ๋‹ค!

์ฃผ์š” ์šด์˜์ž ๊ตฌ์„ฑ์œผ๋กœ ๋„˜์–ด ๊ฐ‘์‹œ๋‹ค. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ตฌ์„ฑํ•˜๋Š” ๋ฐ๋Š” ๋‘ ๊ฐ€์ง€ ๊ธฐ๋ณธ ์ ‘๊ทผ ๋ฐฉ์‹์ด ์žˆ์Šต๋‹ˆ๋‹ค.

  1. ๋ช…๋ น์ค„ ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜์‹ญ์‹œ์˜ค.
  2. ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์‹ญ์‹œ์˜ค.

๋ช…๋ น์ค„ ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ฐ์ดํ„ฐ ์œ ํ˜• ์ง€์› ๋ฐ ๊ฒ€์ฆ์„ ํ†ตํ•ด ์„ค์ •์„ ๋ณด๋‹ค ์œ ์—ฐํ•˜๊ฒŒ ์ฝ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Python์˜ ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—๋Š” ๋ชจ๋“ˆ์ด ์žˆ์Šต๋‹ˆ๋‹ค. 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()

๋ฐ˜๋ฉด, ์ฟ ๋ฒ„๋„คํ‹ฐ์Šค์—์„œ๋Š” ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํฌ๋“œ์— ๋Œ€ํ•œ ์„œ๋น„์Šค ์ •๋ณด๋ฅผ ์ปจํ…Œ์ด๋„ˆ ๋‚ด๋ถ€๋กœ ์‰ฝ๊ฒŒ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋‹ค์Œ ๊ตฌ์„ฑ์„ ์‚ฌ์šฉํ•˜์—ฌ Pod๊ฐ€ ์‹คํ–‰ ์ค‘์ธ ๋„ค์ž„์ŠคํŽ˜์ด์Šค์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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

์—ฐ์‚ฐ์ž ๋…ผ๋ฆฌ

ConfigMap๊ณผ Secret์„ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ๋ถ„๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์ดํ•ดํ•˜๊ธฐ ์œ„ํ•ด ํŠน์ˆ˜ ๋งต์„ ์‚ฌ์šฉํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ๊ฐ์ฒด๋ฅผ ์ถ”์ ํ•˜๊ณ  ์ƒ์„ฑํ•˜๋Š” ๋ฐ ํ•„์š”ํ•œ ๋ฉ”์„œ๋“œ๊ฐ€ ๋ฌด์—‡์ธ์ง€ ์ดํ•ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

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: Python์šฉ kubernetes ํด๋ผ์ด์–ธํŠธ์—๋Š” ์ž์ฒด ๋ฒ„์ „์ด ์žˆ์Šต๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ ๋ฒ„์ „๊ณผ Kubernetes ๋ฒ„์ „ ๊ฐ„์˜ ํ˜ธํ™˜์„ฑ์— ๋Œ€ํ•œ ์ž์„ธํ•œ ๋‚ด์šฉ์€ ๋‹ค์Œ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ˜ธํ™˜์„ฑ ๋งคํŠธ๋ฆญ์Šค.

์ด์ œ ์šฐ๋ฆฌ ํ”„๋กœ์ ํŠธ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค:

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

๋„์ปค์™€ ํ—ฌ๋ฆ„

Dockerfile์€ ๋†€๋ผ์šธ ์ •๋„๋กœ ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ๋ณธ python-alpine ์ด๋ฏธ์ง€๋ฅผ ๊ฐ€์ ธ์™€ ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค. ๋” ๋‚˜์€ ๋•Œ๊นŒ์ง€ ์ตœ์ ํ™”๋ฅผ ์—ฐ๊ธฐํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

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 }}

ํ•ฉ๊ณ„

๊ทธ๋ž˜์„œ ์šฐ๋ฆฌ๋Š” ๋‘๋ ค์›€์ด๋‚˜ ๋น„๋‚œ, Go๋ฅผ ๋ฐฐ์šฐ์ง€ ์•Š๊ณ ๋„ Python์—์„œ Kubernetes์šฉ ์—ฐ์‚ฐ์ž๋ฅผ ์ž์ฒด์ ์œผ๋กœ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ๋ฌผ๋ก , ์—ฌ์ „ํžˆ ์„ฑ์žฅํ•  ์—ฌ์ง€๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฏธ๋ž˜์—๋Š” ์—ฌ๋Ÿฌ ๊ทœ์น™์„ ์ฒ˜๋ฆฌํ•˜๊ณ , ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ์—์„œ ์ž‘์—…ํ•˜๊ณ , CRD์˜ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ๋…๋ฆฝ์ ์œผ๋กœ ๋ชจ๋‹ˆํ„ฐ๋งํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ฝ”๋“œ๋ฅผ ์ž์„ธํžˆ ์‚ดํŽด๋ณด๊ธฐ ์œ„ํ•ด ์ฝ”๋“œ๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋„ฃ์—ˆ์Šต๋‹ˆ๋‹ค. ๊ณต๊ฐœ ์ €์žฅ์†Œ. Python์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌํ˜„๋œ ๋” ์‹ฌ๊ฐํ•œ ์—ฐ์‚ฐ์ž์˜ ์˜ˆ๋ฅผ ์›ํ•œ๋‹ค๋ฉด mongodb ๋ฐฐํฌ๋ฅผ ์œ„ํ•œ ๋‘ ์—ฐ์‚ฐ์ž์— ์ฃผ์˜๋ฅผ ๋Œ๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค(ะฟะตั€ะฒั‹ะน ะธ ์ดˆ).

์ถ”์‹ : Kubernetes ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ธฐ์—๋Š” ๋„ˆ๋ฌด ๊ฒŒ์œผ๋ฅด๊ฑฐ๋‚˜ Bash ์‚ฌ์šฉ์— ๋” ์ต์ˆ™ํ•ด์ง„ ๊ฒฝ์šฐ, ์šฐ๋ฆฌ ๋™๋ฃŒ๋“ค์ด ๋‹ค์Œ ํ˜•์‹์œผ๋กœ ๊ธฐ์„ฑ ์†”๋ฃจ์…˜์„ ์ค€๋น„ํ–ˆ์Šต๋‹ˆ๋‹ค. ์‰˜ ์—ฐ์‚ฐ์ž (์šฐ๋ฆฌ ๋ฐœํ‘œ XNUMX์›”์—์š”).

PPS

๋ธ”๋กœ๊ทธ์—์„œ๋„ ์ฝ์–ด๋ณด์„ธ์š”.

์ถœ์ฒ˜ : habr.com

์ฝ”๋ฉ˜ํŠธ๋ฅผ ์ถ”๊ฐ€