Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

Kind의 Kubernetes 노드 이미지 직접 생성하기

Kubernetes 컨트롤러의 상세한 동작을 확인하고 싶은 일이 있었다. 로그 레벨을 올려서 확인해 보았지만, 모든 로그가 남는 건 아니어서 원하는 세부 사항을 파악하긴 어려웠다.

결국 동작을 제대로 확인하려면 컨트롤러에 직접 로그를 추가해서 돌려봐야 알 수 있겠다는 생각이 들었다. 이전에 스터디했던(당시에 잘 이해 못 했지만) kubebuilder를 이용해서 Kuberentes의 컨트롤러를 올려보려고 했지만, 커스텀 컨트롤러를 만드는 프레임워크라서 내용도 복잡해서 몇 시간 문서를 보고 파악해 보려다가 포기했다. Kubernetes의 컨트롤러를 그대로 가져와서 띄우고 싶은 거라 내가 필요한 것보다 내용이 복잡했다.

그다음으로는 Kubernets에서 sample-controller 예제를 제공하고 있어서 이를 확인해 봤다. 예전 스터디할 때도 비슷하게 했던 기억이 났는데 sample-controller를 이용하면 로컬에서 띄운 컨트롤러를 연결해서 동작을 확인해 볼 수 있으므로 더 쉽게 확인할 수 있을 것 같았다. sample-controller 자체는 쉽게 연결할 수 있었는데 Kubernetes의 컨트롤러 소스 코드를 sample-controller 형태로 가져오는 게 쉽지 않았다. Kubernetes 전체 프로젝트에 연결된 의존성도 있어서 더 어려웠기에 반대로 Kubernetes 소스 코드 안에 있는 컨트롤러 코드를 sample-controller처럼 수정해서 컨트롤러를 빌드하려고 시도했으나 이마저도 실패했다. 내가 Go 언어에 대한 지식이 더 많았다면 가능했을지도 모르지만 나한테는 쉽지 않았다.

kubebuilder나 sample-controller나 단일 CRD와 컨트롤러를 만들어서 연결하는데 Kubernetes에 내장 컨트롤러는 kube-controller-manager로 합쳐져서 빌드되기 때문에 내 지식으론 따로 빌드하기가 쉽지 않았다. 컨트롤러도 각각 개발해야 하니 Kuberentes 프로젝트에 개별 컨트롤러를 빌드해서 테스트하는 방법이 있을 것 같았는데 문서를 찾지는 못했다.

Kind의 노드 이미지 직접 생성하기

결국 내가 원하는 건 내가 추가 로깅을 하도록 수정한 컨트롤러를 Kubernetes 클러스터 내에서 실행하는 것이므로 수정한 코드로 띄울 수만 있다면 kube-controller-manager를 다 새로 띄워도 크게 상관없고 동작 면에서 오히려 이게 더 나을 수도 있었다. CronJob을 테스트하려는 거라 CRD 대신 내장된 리소스를 써야 하는 부분도 있었다. 그래서 Kind에서 직접 빌드한 Kubernetes 이미지를 사용하는 법을 찾아봤다.

Kind에서는 아래처럼 control-plane이나 work에서 사용할 Kubernetes 이미지를 지정하고 이는 Kind 릴리스 공지에 버전 안내가 나와 있다.

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  image: kindest/node:v1.24.7@sha256:577c630ce8e509131eab1aea12c022190978dd2f745aac5eb1fe65c0807eb315
- role: worker
  image: kindest/node:v1.24.7@sha256:577c630ce8e509131eab1aea12c022190978dd2f745aac5eb1fe65c0807eb315

Node Image 문서를 보면 노드 이미지를 직접 빌드하는 방법이 나와 있다.(이것저것 찾아볼 때 보긴 했는데 문서가 자세히 나와 있진 않아서 넘어갔다) kind에서 친절하게도 직접 Kubernetes 이미지에서 노드 이미지를 빌드할 수 있는 kind build node-image 명령어를 제공한다.

$ kind build node-image -h
Build the node image which contains Kubernetes build artifacts and other kind requirements

Usage:
  kind build node-image [kubernetes-source] [flags]

Flags:
      --arch string         architecture to build for, defaults to the host architecture
      --base-image string   name:tag of the base image to use for the build (default "docker.io/kindest/base:v20221025-014d1502")
  -h, --help                help for node-image
      --image string        name:tag of the resulting image to be built (default "kindest/node:latest")
      --kube-root string    DEPRECATED: please switch to just the argument. Path to the Kubernetes source directory (if empty, the path is autodetected)
      --type string         build type, default is docker (default "docker")

Global Flags:
      --loglevel string   DEPRECATED: see -v instead
  -q, --quiet             silence all stderr output
  -v, --verbosity int32   info log verbosity, higher value produces more output

이전에는 --kube-root 플래그를 썼던 것 같지만 이젠 명령어 인자로 Kubernetes 소스의 위치를 전달하면 된다.

빌드를 위해 kubernetes 소스 코드를 클론 받고 내가 원하는 버전인 v1.24.10로 바꾸었다.

$ git clone https://github.com/kubernetes/kubernetes

$ git checkout v1.24.10

이제 kubernetes 소스 코드가 있는 곳에서 kind build node-image ./로 이미지를 빌드한다. 다른 곳에서 실행한다면 마지막 인자로 소스 코드의 경로를 지정하면 된다.

$ kind build node-image ./
Starting to build Kubernetes
+++ [0320 01:07:07] Verifying Prerequisites....
+++ [0320 01:07:07] Using docker on macOS
+++ [0320 01:07:07] Building Docker image kube-build:build-9be49182ee-5-v1.24.0-go1.19.5-bullseye.0
+++ [0320 01:07:10] Syncing sources to container
+++ [0320 01:07:11] Running build command...
+++ [0320 01:07:14] Building go targets for linux/arm64
    k8s.io/kubernetes/hack/make-rules/helpers/go2make (non-static)
+++ [0320 01:07:18] Generating prerelease lifecycle code for 26 targets
+++ [0320 01:07:21] Generating deepcopy code for 236 targets
+++ [0320 01:07:24] Generating defaulter code for 92 targets
+++ [0320 01:07:30] Generating conversion code for 129 targets
+++ [0320 01:07:39] Generating openapi code for KUBE
+++ [0320 01:07:54] Generating openapi code for AGGREGATOR
+++ [0320 01:07:55] Generating openapi code for APIEXTENSIONS
+++ [0320 01:07:56] Generating openapi code for CODEGEN
+++ [0320 01:07:57] Generating openapi code for SAMPLEAPISERVER
+++ [0320 01:07:58] Building go targets for linux/arm64
    k8s.io/kubernetes/cmd/kube-apiserver (static)
    k8s.io/kubernetes/cmd/kube-controller-manager (static)
    k8s.io/kubernetes/cmd/kube-scheduler (static)
    k8s.io/kubernetes/cmd/kube-proxy (static)
    k8s.io/kubernetes/cmd/kubeadm (static)
    k8s.io/kubernetes/cmd/kubectl (static)
    k8s.io/kubernetes/cmd/kubelet (non-static)
+++ [0320 01:08:12] Syncing out of container
+++ [0320 01:08:16] Building images: linux-arm64
+++ [0320 01:08:16] Starting docker build for image: kube-apiserver-arm64
+++ [0320 01:08:16] Starting docker build for image: kube-controller-manager-arm64
+++ [0320 01:08:16] Starting docker build for image: kube-scheduler-arm64
+++ [0320 01:08:16] Starting docker build for image: kube-proxy-arm64
+++ [0320 01:08:23] Deleting docker image registry.k8s.io/kube-scheduler-arm64:v1.24.10-dirty
+++ [0320 01:08:23] Deleting docker image registry.k8s.io/kube-proxy-arm64:v1.24.10-dirty
+++ [0320 01:08:25] Deleting docker image registry.k8s.io/kube-controller-manager-arm64:v1.24.10-dirty
+++ [0320 01:08:25] Deleting docker image registry.k8s.io/kube-apiserver-arm64:v1.24.10-dirty
+++ [0320 01:08:25] Docker builds done
Finished building Kubernetes
Building node image ...
Building in container: kind-build-1679242110-1920456844
Image "kindest/node:latest" build completed.

마지막 메시지에서 보듯이 kindest/node:latest로 빌드되고 kind는 로컬의 Docker를 그대로 사용하므로 이 이미지를 사용할 수 있다.

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  image: kindest/node:latest
- role: worker
  image: kindest/node:latest

이제 kind create cluster --config kind-config.yaml로 클러스터를 생성하고 kube-controller-manager의 로그를 확인하면 아래처럼 수정된 로그(Starting cronjob controller v2Starting custom cronjob controller v2로 변경했다.)를 확인할 수 있다.

I0319 16:05:33.714932       1 controllermanager.go:564] Starting "cronjob"
I0319 16:05:33.717930       1 controllermanager.go:593] Started "cronjob"
I0319 16:05:33.717994       1 cronjob_controllerv2.go:135] "Starting custom cronjob controller v2"
2023/03/20 02:15 2023/03/20 02:15