kind로 쿠버네티스 개발환경 만들기

KinD (Kubernetes in Docker)

여러가지 MSA 패턴들을 구현해보기 위해 간단하게 띄워볼 수 있는 쿠버네티스 클러스터를 찾다가 알게 되었다. 본 포스팅을 위한 예시 코드 저장소를 참고해도 좋다.

맥이라면 간단한게 brew로 kind를 사용할 수 있다. (Docker Desktop에서도 사용할 수 있지만, CLI 를 추천한다.)

brew install kind

Provisioning KinD Cluster

아래처럼 yaml 파일로 클러스터를 정의할 수 있다. 컨트롤 플레인과 워커 노드를 여러 대 구성할 수도 있지만, 로컬 자원 절약을 위해 컨트롤 플레인 1대만 구성했다.

# cluster.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
  - role: control-plane
    extraPortMappings:
      - containerPort: 30080
        hostPort: 30080
        protocol: TCP
      - containerPort: 30443
        hostPort: 30443
        protocol: TCP

기본적으로 디폴트로 사용되는 아주 미니멀한 CNI(kindnetd)가 존재한다. 이번엔 로컬에서 MSA를 서빙하는 게 목적이라 Gateway 어플리케이션만 NodePort로 노출시키는 방식으로 구성했다.

kind create cluster --config=cluster.yaml

이렇게 간단한 명령으로 로컬 테스트용 쿠버네티스를 올려볼 수 있다.

kubectl get nodes -o wide
NAME                 STATUS   ROLES           AGE   VERSION   INTERNAL-IP   EXTERNAL-IP   OS-IMAGE                         KERNEL-VERSION    CONTAINER-RUNTIME
kind-control-plane   Ready    control-plane   15d   v1.32.2   172.18.0.2    <none>        Debian GNU/Linux 12 (bookworm)   6.12.5-linuxkit   containerd://2.0.2

Ingress Nginx

# ingress-nginx-values.yaml
controller:
  service:
    type: NodePort
    nodePorts:
      http: 30080
      https: 30443

아주 간단하게 http, https를 노출시키는 인그레스를 정의했다. 공식 문서 상에서는 Kong 등 다양한 인그레스를 사용할 수 있다고 되어있지만, 매우 간단하게 익숙한 것으로 진행했다.

helm upgrade --install ingress-nginx ingress-nginx \
  --repo https://kubernetes.github.io/ingress-nginx \
  --namespace ingress-nginx --create-namespace \
  -f ingress-nginx-values.yaml

ArgoCD

GitOps

Argo CD is a declarative, GitOps continuous delivery tool for Kubernetes.

아마 요즘 쿠버네티스를 사용하는 많은 조직이 사용하는 툴인데, 깃옵스(GitOps) 의 예시를 만들기 위해서 설치했다. 아래의 yaml 파일과 helm으로 배포했다.

global:
  domain: argo.example.com

configs:
  params:
    server.insecure: true
  cm:
    exec.enabled: true

certificate:
  enabled: false

server:
  ingress:
    enabled: true
    ingressClassName: nginx
    paths:
      - /
    pathType: Prefix
    annotations:
      nginx.ingress.kubernetes.io/force-ssl-redirect: "false"
      nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
    tls: []

설치하면 바로 접근할 수 있다. (설정에 따라 포트포워딩을 해야할 수도 있겠지만.)

Argo Secrets

예시에서는 Argo가 깃헙 저장소에 접근해야 하기 때문에, 깃헙의 Personal Access Token을 발급받아서 설정해두어야 한다. 토큰은 repository 읽기 권한과 package 읽기 권한을 포함해야 한다. 설정 방법은 이 문서를 참조한다.

github의 컨테이너 레지스트리에 접근하기 위해서는 아래처럼 시크릿도 생성해줘야 한다. (원한다면 위 토큰과 분리하셔도 좋다.)

kubectl create secret docker-registry ghcr-secret --docker-server=ghcr.io --docker-username={USER-NAME} --docker-password={ghp_xxx} --docker-email={EMAIL-ADDRESS} -n default

Github Packages - Container Registry

이 프로젝트에서는 지출없는 환경 설정을 위해 Github 에서 제공하는 Container Registry를 사용한다. 아래는 Github Actions 를 위한 스크립트다.

name: Build

on:
  push:
    branches:
      - main
      - develop
    paths:
      - 'gateway/**'
      - 'inventory-api/**'
      - 'order-api/**'

jobs:
  build:
    runs-on: ubuntu-24.04-arm
    strategy:
      matrix:
        app: [
          'gateway',
          'order-api',
          'inventory-api',
        ]
    permissions:
      contents: read
      packages: write

    outputs:
      image_tag_matrix: ${{ steps.set-matrix.outputs.result }}

    steps:
      - uses: actions/checkout@v4
        name: Checkout
        with:
          ref: ${{ github.head_ref }}
          fetch-depth: 0

      - name: Determine Version
        id: version
        run: bash .github/workflows/version.bash

      - name: SetUp to Docker Buildx
        uses: docker/setup-buildx-action@v3
        with:
          buildkitd-config: .github/buildkitd.toml
          platforms: linux/amd64,linux/arm64

      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build And Push
        uses: docker/build-push-action@v6
        env:
          REGISTRY: ghcr.io
          IMAGE_NAME: ${{ github.repository }}-${{ matrix.app }}
          IMAGE_TAG: '${{ steps.version.outputs.semVer }}-${{ steps.version.outputs.shortSha }}'
        with:
          file: Dockerfile
          context: .
          push: true
          platforms: linux/arm64
          tags: |
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest            
          cache-from: type=gha
          cache-to: type=gha,mode=max
          build-args: |
            APP_NAME=${{ matrix.app }}            

여기서 약간 특이한 것은, Apple Silicon 환경에서 작업하다보니, arm 기반의 클러스터가 만들어졌다는 점이다. 여기서 앱들을 돌리려면 이미지 빌드도 arm 을 지원해야 한다.

As a result

kind
└── apps
    ├── charts
    │   ├── app
    │   │   └── templates
    │   ├── mysql-arm64
    │   │   └── templates
    │   └── redis-arm64
    │       └── templates
    ├── configs
    │   ├── gateway
    │   ├── inventory-api
    │   ├── mysql-inventory-api
    │   ├── mysql-order-api
    │   ├── order-api
    │   └── redis
    └── values
        ├── gateway
        ├── inventory-api
        ├── mysql-inventory-api
        ├── mysql-order-api
        ├── order-api
        └── redis

이 Helm Chart를 기반으로 앱을 나누고, 설정과 값을 분리해서 관리하한다.

이렇게 디렉토리를 나눈 이유는, Helm 템플릿 구조와 실제 값들을 명확히 구분해서 관리하고, ArgoCD나 CI 파이프라인 등과 연동할 때 각각의 책임을 분리하기 위함이다. 예를 들어 동일한 차트를 여러 서비스에 재활용하면서 값만 바꿔서 배포하고 싶을 때 매우 유용하다.

원하는대로 helm 차트를 정의하고, appset을 생성하면 된다.

kubectl apply -f apps/appset.yaml -n argocd

Result

초기 환경이 약간 복잡하지만… https://github.com/kubernetes-sigs/kind/issues/3508 같은 스냅샷 기능이 잘 나오기만 하면 여기저기서 쓸 수 있는 걸 볼 수 있지 않을까?