Kubernetes 이야기

Kopf (소개) 본문

개발/python

Kopf (소개)

kmaster 2023. 2. 3. 23:31
반응형

Kubernetes Operator는 내부에 구현된 일부 도메인 로직을 사용하여 특정 종류의 개체를 오케스트레이션하는 일종의 컨트롤러이다.

Operator에 대해서는 아래 글을 참고한다.

https://kmaster.tistory.com/97

 

Kubebuilder ( Kubernetes operator )

오퍼레이터(Operator)는 사용자 정의 리소스 (CR)를 사용하여 애플리케이션 및 해당 컴포넌트를 관리하는 쿠버네티스의 소프트웨어 확장 기능이다. 오퍼레이터는 쿠버네티스 원칙, 특히 컨트롤 루

kmaster.tistory.com

 

Go 언어에서는 Kubebuilder나 OperatorSDK와 같은 프레임워크를 사용한다. Python에서는 Kopf라는 프레임워크가 존재한다.

 

다른 프레임워크와 마찬가지로 Kopf는 오퍼레이터를 실행하고, 쿠버네티스 클러스터와 통신하고, 쿠버네티스 이벤트를 Kopf 기반 오퍼레이터의 순수 Python 기능으로 마샬링하기 위한 "외부" 툴킷과 "내부" 라이브러리를 모두 제공한다. 

 

아키텍처

https://kopf.readthedocs.io/en/stable/architecture/

주요 역할

  • Core : Kopf 기반 운영자가 사용하는 주요 기능으로, Core가 운영자를 움직이게 한다.
  • Cogs : 거의 모든 모듈의 프레임워크 전체에서 사용되는 유틸리티이다. 
  • Kits : 일부 시나리오 및/또는 설정을 위해 운영자 개발자에게 제공되는 유틸리티 및 특수 도구이다. 프레임워크 자체는 이를 사용하지 않는다.

설치

# pipenv --python 3.11
# pipenv install kopf[uvloop]

 

예제

이번 예제는 Tekton Pipeline 을 실행하는 CRD와 이 CRD를 처리하는 오퍼레이터를 만들어보자. ( 파이프라인을 실행하는 용도이기 때문에 생성만 실습한다. )

 

Tekton Pipeline 예제는 다음을 참고한다.

https://kmaster.tistory.com/139

 

tekton과 argocd를 이용한 GitOps 구축하기

tekton과 argocd를 사용하여 GitOps 구축을 해보자. 전체적인 구성은 다음과 같다. 전체적인 과정을 보면 1) Tekton을 이용하여 소스를 빌드 후 Registry에 저장한다. 2) Manifest를 저장하는 Git 저장소에 빌드

kmaster.tistory.com

 

실행할 PipelineRun은 다음과 같다.

apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
  generateName: pipelinerun-
spec:
  serviceAccountName: tekton-sa
  pipelineRef:
    name: pipeline
  params:
    - name: giturl
      value: https://github.com/kmaster8/flask-helloworld.git
  workspaces:
  - name: shared-data
    volumeClaimTemplate:
      spec:
        accessModes:
        - ReadWriteOnce
        resources:
          requests:
            storage: 1Gi

 

여기서 CRD로 입력받을 값은 github URL이다.

 

다음과 같은 CRD를 만들어보자. 

(CRD 생성법은 다음을 참고한다. https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/)

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: tektonpipelineruns.kmaster8.com
spec:
  group: kmaster8.com
  names:
    kind: TektonPipelineRun
    plural: tektonpipelineruns
    singular: tektonpipelinerun
    shortNames:
      - tpr
  scope: Namespaced
  versions:
  - name: v1beta1
    schema:
      openAPIV3Schema:
        properties:
          spec:
            properties:
              giturl:
                type: string
            required:
            - giturl
            type: object
          status:
            type: object
            x-kubernetes-preserve-unknown-fields: true
        type: object
    served: true
    storage: true
    additionalPrinterColumns:
    - name: GitUrl
      type: string
      jsonPath: .spec.giturl
      description: Git repository

 

생성 후 조회를 해보자.

# k get tektonpipelineruns.kmaster8.com -A
No resources found

 

이제 다음과 같은 CR을 생성해 보자.

apiVersion: kmaster8.com/v1beta1
kind: TektonPipelineRun
metadata:
  name: run1
spec:
  giturl: "https://github.com/kmaster8/flask-helloworld.git"

 

생성 후 조회해 보자.

# k get tektonpipelineruns.kmaster8.com -A
NAMESPACE   NAME   GITURL
test        run1   https://github.com/kmaster8/flask-helloworld.git

상세정보는 다음과 같다.

# k describe tpr run1
Name:         run1
Namespace:    test
Labels:       <none>
Annotations:  <none>
API Version:  kmaster8.com/v1beta1
Kind:         TektonPipelineRun
Metadata:
  Creation Timestamp:  2023-02-03T12:54:07Z
  Generation:          1
  Managed Fields:
    API Version:  kmaster8.com/v1beta1
    Fields Type:  FieldsV1
    fieldsV1:
      f:spec:
        .:
        f:giturl:
    Manager:         kubectl-create
    Operation:       Update
    Time:            2023-02-03T12:54:07Z
  Resource Version:  221474456
  UID:               cd8d06fb-5965-4fe6-8494-fccfcf055a9b
Spec:
  Giturl:  https://github.com/kmaster8/flask-helloworld.git
Events:    <none>

 

이제 Kopf 에서 생성 이벤트를 살펴보자. 아직 아무 쓸모 없는 뼈대이다.

import kopf

@kopf.on.create('kmaster8.com', 'v1beta1', 'tektonpipelineruns')
def create_fn(spec, **kwargs):
    print(f"Creating: {spec}")
    return {'message': 'done'}

 

실행을 해보면 다음과 같다.

# kopf run create.py
/root/.local/share/virtualenvs/kopf-cdlYO4Rx/lib/python3.11/site-packages/kopf/_core/reactor/running.py:176: FutureWarning: Absence of either namespaces or cluster-wide flag will become an error soon. For now, switching to the cluster-wide mode for backward compatibility.
  warnings.warn("Absence of either namespaces or cluster-wide flag will become an error soon."
[2023-02-03 21:55:27,579] kopf._core.engines.a [INFO    ] Initial authentication has been initiated.
[2023-02-03 21:55:27,586] kopf.activities.auth [INFO    ] Activity 'login_with_kubeconfig' succeeded.
[2023-02-03 21:55:27,586] kopf._core.engines.a [INFO    ] Initial authentication has finished.
Creating: {'giturl': 'https://github.com/kmaster8/flask-helloworld.git'}
[2023-02-03 21:55:28,782] kopf.objects         [INFO    ] [test/run1] Handler 'create_fn' succeeded.
[2023-02-03 21:55:28,783] kopf.objects         [INFO    ] [test/run1] Creation is processed: 1 succeeded; 0 failed.

실행 후 상세 정보를 보자.

# k describe tpr run1
Name:         run1
Namespace:    test
Labels:       <none>
Annotations:  kopf.zalando.org/last-handled-configuration: {"spec":{"giturl":"https://github.com/kmaster8/flask-helloworld.git"}}
API Version:  kmaster8.com/v1beta1
Kind:         TektonPipelineRun
Metadata:
  Creation Timestamp:  2023-02-03T12:54:07Z
  Generation:          2
...
Spec:
  Giturl:  https://github.com/kmaster8/flask-helloworld.git
Status:
  create_fn:
    Message:  done
Events:
  Type    Reason   Age   From  Message
  ----    ------   ----  ----  -------
  Normal  Logging  49s   kopf  Creation is processed: 1 succeeded; 0 failed.
  Normal  Logging  49s   kopf  Handler 'create_fn' succeeded.

Status에 Message: done 이 추가된 것을 확인할 수 있다.

 

debug 메시지를 보기위해서는 kopf run create.py --verbose 로 실행하면 상세한 로그를 조회할 수 있다.

 

연산자를 중단하고 다시 시작하면 이미 성공적으로 처리되었으므로 운영자는 개체를 처리하지 않는다. 

 

이제 실제 Tekton Pipeline을 실행하는 로직을 추가해 보자. 먼저 kubernetes api 를 설치해보자.

# pipenv install kubernetes

다음과 같은 생성 함수를 만들어보자.

import kopf
import yaml
import kubernetes.client
from kubernetes.client.rest import ApiException

@kopf.on.create('kmaster8.com', 'v1beta1', 'tektonpipelineruns')
def create_fn(spec, **kwargs):
    doc = get_yaml(spec, **kwargs)

    kopf.adopt(doc)

    try:
      api = kubernetes.client.CustomObjectsApi()
      group="tekton.dev"
      version="v1beta1"
      plural="pipelineruns"
      crd = api.create_namespaced_custom_object(group=group,version=version,namespace=doc['metadata']['namespace'], plural=plural, body=doc)
      return {'children': [crd['metadata']['uid']]}
    except ApiException as e:
      print("Exception when calling AppsV1Api->create_namespaced_deployment: %s\n" % e)


def get_yaml(spec, **kwargs):
    # Create the deployment spec
    doc = yaml.safe_load(f"""
      apiVersion: tekton.dev/v1beta1
      kind: PipelineRun
      metadata:
        generateName: pipelinerun-
      spec:
        serviceAccountName: tekton-sa
        pipelineRef:
          name: pipeline
        params:
          - name: giturl
            value: {spec.get('giturl')}
        workspaces:
        - name: shared-data
          volumeClaimTemplate:
            spec:
              accessModes:
              - ReadWriteOnce
              resources:
                requests:
                  storage: 1Gi
    """)
    return doc

 

 

[실행결과]

 

다음과 같이 Tekton Pipeline이 실행된 것을 볼 수 있다.

# k get pipelineruns.tekton.dev -A
NAMESPACE   NAME                SUCCEEDED   REASON      STARTTIME   COMPLETIONTIME
test        pipelinerun-2dfrg   True        Succeeded   52s         23s

Status에는 PipelineRun 의 uid가 저장된 것을 볼 수 있다.

...
Status:
  create_fn:
    Children:
      91ab3a95-6920-4659-8307-6104faa451dc

 

여기서, 생성된 pipelineruns 를 살펴보자.

apiVersion: tekton.dev/v1beta1
kind: PipelineRun
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"tekton.dev/v1beta1","kind":"Pipeline","metadata":{"annotations":{},"name":"pipeline","namespace":"test"},"spec":{"params":[{"description":"THIS PARAMETER COMING FROM TRIGGER TEMPLATE\n","name":"giturl"}],"tasks":[{"name":"fetch-repo","params":[{"name":"url","value":"$(params.giturl)"}],"taskRef":{"name":"git-clone"},"workspaces":[{"name":"output","workspace":"shared-data"}]},{"name":"build-container-image","params":[{"name":"app_repo","value":"dir:///workspace/output/"},{"name":"container_image","value":"ghcr.io/kmaster8/myimage"},{"name":"container_tag","value":"$(tasks.fetch-repo.results.commit)"}],"runAfter":["fetch-repo"],"taskRef":{"name":"build-kaniko-git"},"workspaces":[{"name":"output","workspace":"shared-data"}]},{"name":"manifest-update","params":[{"name":"imageurl","value":"ghcr.io/kmaster8/myimage"},{"name":"imagetag","value":"$(tasks.fetch-repo.results.commit)"}],"runAfter":["build-container-image"],"taskRef":{"name":"manifest-deploy"}}],"workspaces":[{"description":"This workspace will receive the cloned git repo and be passed\nto the next Task for the repo's README.md file to be read.\n","name":"shared-data"}]}}
  creationTimestamp: "2023-02-03T13:56:40Z"
  generateName: pipelinerun-
...
  ownerReferences:
  - apiVersion: kmaster8.com/v1beta1
    blockOwnerDeletion: true
    controller: true
    kind: TektonPipelineRun
    name: run1
    uid: 5aa4c20d-a991-4993-a006-f9e45f50d799
  resourceVersion: "221494762"
  uid: f5444346-23cd-4fc0-a1e8-3994503caf87
...

위와 같이 ownerReferences가 추가된 것을 볼 수 있다. 이건 create.py 에서 다음과 같이 넣어주면 

kopf.adopt(doc)

ownerReferences 가 추가된다. tektonepipelinerun CR을 삭제하면 pipelinerun CR이 삭제될 것이다.

 

K8S API 서버에 호출하는 경우는 다음과 같이 할 수 있다.

# curl -k -H "Authorization: Bearer $TOKEN" https://localhost:6443/apis/kmaster8.com/v1beta1 --insecure
{
  "kind": "APIResourceList",
  "apiVersion": "v1",
  "groupVersion": "kmaster8.com/v1beta1",
  "resources": [
    {
      "name": "tektonpipelineruns",
      "singularName": "tektonpipelinerun",
      "namespaced": true,
      "kind": "TektonPipelineRun",
      "verbs": [
        "delete",
        "deletecollection",
        "get",
        "list",
        "patch",
        "create",
        "update",
        "watch"
      ],
      "shortNames": [
        "tpr"
      ],
      "storageVersionHash": "kaNzSmQan7Y="
    }
  ]
}

 

조회

curl -k -H "Authorization: Bearer $TOKEN" https://localhost:6443/apis/kmaster8.com/v1beta1/namespaces/test/tektonpipelineruns --insecure

 

생성

curl -v -k -X POST \
        -H "Authorization: Bearer $TOKEN" \
        -H "Content-type: application/yaml" \
        --data-binary @cr.yaml \
        https://localhost:6443/apis/kmaster8.com/v1beta1/namespaces/test/tektonpipelineruns --insecure
반응형

'개발 > python' 카테고리의 다른 글

Python에서 gRPC 구현  (0) 2023.02.13
Kopf ( 예제 )  (1) 2023.02.05
Ubuntu에서 pipenv 실행 시 FileNotFoundError 오류  (0) 2023.01.28
Loguru  (0) 2023.01.24
Fastapi 모범 사례  (0) 2023.01.22
Comments