[k8s] kube-rs로 operator 만들어보기 - 2

이전 포스트
https://blog.naver.com/sssang97/223461756266

이번에는 CustomResource를 이용해서 이전보다 복잡도가 있는 operator를 만들어보겠다.

내가 만들어볼 것은 초단위로 컨테이너를 트리거하는 cronjob이다. 기본 cronjob은 분 단위까지밖에 제어가 되지 않기 때문에 한번 시험삼아 만들어봤다.




CRD 정의하기

Custom Resource를 띄우려면 먼저 그에 대한 정의인 CustomResourceDefinition이 필요하다.
조금 더 자세한 내용은 별도 포스트를 참조한다.
https://blog.naver.com/sssang97/223114653465


우리가 가장 먼저 해야할 것은 저기에 새 crd가 추가되게끔 하는 것이다.


그러려면 눈알빠질거같은 yaml을 말아서 올려줘야 하는데..

다행히도 kube-rs는 그에 대한 좀 세련된 generate 기능을 제공한다.

use kube_derive::CustomResource;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

#[derive(CustomResource, Clone, Debug, Deserialize, Serialize, JsonSchema)]
#[kube(
    group = "myyrakle.com",
    version = "v1",
    kind = "SecondCronJob",
    namespaced
)]
pub struct SecondCronJobSpec {
    pub name: String,
    pub image: String,
    pub interval_seconds: i32,
}

이렇게 스키마 짜고 api 날리면 된다.
이 경우에는 이름, 도커 이미지, 반복 간격 정도만 받았다. 저게 spec에 들어갈 옵션들이다.

그리고 operator 코드 진입부에서는 바로 CRD를 만들어주도록 했다.

이미 있다면 삭제하고


바로 만든다.

실행될때마다 crd를 덮어씌우도록 했다.




신규 리소스 감지하기

이전 포스트에서 Pod를 감지한 것과 거의 같은 원리다.

이렇게 해서


실행하고


잘 말아놓은 커스텀 리소스를 apply로 퍼올리면


딱 반응이 들어올 것이다.

근데 보기만 해서는 별 쓸모가 없을 것이다.
동시에 스케줄러를 돌리려면 공유 변수가 있어야 하니, 리소스 맵 테이블을 두고서 리소스가 들어올때마다 삽입을 해주도록 했다.




스케줄러 구현

스케줄러는 막 엄밀한 시간이 보장되도록 구현하지는 않았다.
최대한 단순한 형태로 구현하려고 시도는 했는데.. 생각보다는 좀 복잡해진 것 같다.

매 루프마다 공유 변수에 들어있는 리소스 목록을 쭉 읽어오고,


그걸 기반으로 해서 interval time이 지났거나 실행된 적이 없는 리소스를 새 batch job으로 띄운다.
그리고 실행된거는 실행기록을 last_run_map에 쌓는다.

리스트가 많거나 api call에 딜레이가 생기면 실제 기대시간보다는 좀 늦게 트리거될 수도 있다.




삭제 감지

어째서인지 커스텀 리소스에 대해서는 Delete 이벤트가 watch로 감지되질 않더라.
그래서 프로세스 하나 더 띄워서 10초 간격으로 풀스캔때리면서 대조하도록 구현했다.

없어졌다면 공유 변수에서 제거한다.

k8s 환경에서 권장하는 방법은 finalizer를 이용하는 방식이지만, 여기서는 다루지 않고 넘어간다.
https://kubernetes.io/ko/docs/concepts/overview/working-with-objects/finalizers/




실행해보기

그럼 이제 동작하는지 확인해보자.

실행하고


아까 말아놨던 커스텀리소스를 띄우면


이렇게 계속 job을 띄워줄 것이다.

삭제한다면

삭제하는대로 반응해서 대응한다.




클러스터에 띄우기

이전 포스트에서 다뤘던 방법과 크게 다르지 않다.

일단 도커로 말고

다음과 같이 구성 yaml을 만들어준다.

apiVersion: v1
kind: Namespace
metadata:
  labels:
    control-plane: controller-manager
  name: myyrakle
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: controller-manager
  namespace: myyrakle
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: manager-role
rules:
- apiGroups:
  - ""
  - "myyrakle.com"
  resources:
  - pods
  - services
  - endpoints
  - events
  - configmaps
  - secrets
  - namespaces
  - jobs
  - serviceaccounts
  - secondcronjobs
  verbs:
  - '*'
- apiGroups:
  - "apiextensions.k8s.io"
  resources:
  - customresourcedefinitions
  verbs:
  - '*'
- apiGroups:
  - batch
  resources:
  - jobs
  verbs:
  - '*'
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: manager-rolebinding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: manager-role
subjects:
- kind: ServiceAccount
  name: controller-manager
  namespace: myyrakle
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: controller-manager
  namespace: myyrakle
  labels:
    control-plane: controller-manager
spec:
  selector:
    matchLabels:
      control-plane: controller-manager
  replicas: 1
  template:
    metadata:
      labels:
        control-plane: controller-manager
    spec:
      securityContext:
      containers:
      - command:
        - /usr/bin/kube_test
        image: myyrakle/second-cronjob-operator:v0.1.0
        name: manager
        securityContext:
          allowPrivilegeEscalation: false
        livenessProbe:
          httpGet:
            path: /healthz
            port: 44444
          initialDelaySeconds: 15
          periodSeconds: 20
        readinessProbe:
          httpGet:
            path: /readyz
            port: 44444
          initialDelaySeconds: 5
          periodSeconds: 10
        resources:
          limits:
            cpu: 100m
            memory: 30Mi
          requests:
            cpu: 100m
            memory: 20Mi
      serviceAccountName: controller-manager
      terminationGracePeriodSeconds: 10
---

권한에 필요한게 좀 더 있지만, 크게 다르진 않다.


그리고 띄워보면


오류 없이 로그가 하나씩 찍혀야 하고


커스텀 리소스를 띄우면 띄우는대로


잘 동작해야 한다.



전체 예제 코드는 아래 레포에 있다.
https://github.com/myyrakle/infrastructures/tree/master/k8s/examples/operators/second-cronjob