---
# System prepended metadata

title: 以 kind 在本地架設 k8s 實驗環境
tags: [Docker, kind, k8s]

---

---
title: 以 kind 在本地架設 k8s 實驗環境
tags: Docker，kind，k8s
---

# 以 kind 在本地架設 k8s 實驗環境

## 目錄
[TOC]

---

## kind 是什麼？
kind 是 Kubernetes in Docker 的簡稱，能使用 Docker 模擬出 Kubernetes 叢集的工具。

### 為什麼要用 kind？
* 輕量且快速。
* 完整支援 Kubernetes 功能。
* 支援多節點。

### 與其他工具比較

| 工具 | 啟動速度 | 多節點支援 | 資源消耗 | 適合場景 |
| --- | --- | --- | --- | --- |
| **kind** | 快 | ✅ | 低 | CI / 本地開發實驗 |
| **minikube** | 中 | 有限 | 較高 | 本地單節點開發 |
| **k3d** | 快 | ✅ | 低 | 本地開發、輕量生產模擬 |

> 三者功能相近，kind 的優勢在於與 Docker 整合緊密、CI 環境友好，且官方對 ingress-nginx 的 kind 部署有完整支援。

---

## 安裝 kind

### 前置條件
* 需要確認 Docker 已安裝。

### 在 Mac 安裝 kind
在這裡會使用 Homebrew 進行安裝。
```bash
brew install kind kubectl helm docker
```

### 檢查安裝是否成功
```bash
docker --version && kind --version && kubectl version --client && helm version
```

#### 成功會顯示以下資訊
```
Docker version 27.4.0, build bde2b89
kind version 0.26.0
Client Version: v1.32.1
Kustomize Version: v5.5.0
version.BuildInfo{Version:"v3.17.0", ...}
```

---

## 專案目錄結構

```bash
├── app
│   ├── Dockerfile
│   ├── main.py
│   ├── requirements.txt
│   └── tests
│       └── test_health.py
├── k8s
│   ├── deployment.yaml
│   ├── ingress.yaml
│   ├── namespace.yaml
│   └── service.yaml
├── kind
│   └── kind-config.yaml
├── Makefile
├── note.md
└── README.md
```

---

## 整體架構圖

```
外部 HTTP 請求 (curl http://python.local)
         ↓
  [ Ingress Controller ]
  ingress-nginx (NGINX)
  規則：host=python.local → python-api:8000
         ↓
  [ Service: python-api ]
  ClusterIP，Port 8000
  selector: app=python-api
         ↓
  [ Pod: python-api ]
  FastAPI container，監聽 0.0.0.0:8000
```

| 層級 | 元件 | 說明 |
| --- | --- | --- |
| L7 HTTP Routing | Ingress | 根據 domain/path 決定流量目標 |
| L4 TCP LB | Service | 對應 Pod，提供穩定存取入口 |
| 執行層 | Pod | 實際運行的 FastAPI 應用容器 |

---

## 建立 kind 叢集

### 建立 kind-config.yaml
建立一個 control-plane 節點與一個 worker 節點的 kind 叢集配置。

```yaml
# kind/kind-config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
  - role: control-plane
    extraPortMappings:
      - containerPort: 80
        hostPort: 80
      - containerPort: 443
        hostPort: 443
  - role: worker
```

#### kind-config.yaml 結構說明

| 項目 | 說明 |
| --- | --- |
| `kind: Cluster` | 宣告這是 kind 叢集設定檔 |
| `apiVersion` | kind config 格式版本 |
| `nodes:` | 設定叢集中的節點組成 |
| control-plane node | Kubernetes 管理核心 |
| worker node | 執行 Pod 的節點 |
| extraPortMappings | 將本機 80/443 → kind node 80/443，使 localhost 可直接存取 ingress |

### 啟動 kind 叢集

```bash
kind create cluster --name dev-cluster --config kind/kind-config.yaml
```

#### 指令說明

| 指令部分 | 說明 |
| --- | --- |
| `kind create cluster` | 建立 Kubernetes 叢集，nodes 以 Docker container 形式運行 |
| `--name dev-cluster` | 指定叢集名稱，會建立 context `kind-dev-cluster` |
| `--config kind/kind-config.yaml` | 套用自訂配置，包含 node role、port mapping 等 |

---

## 建立 ingress-nginx

ingress-nginx 是 Kubernetes 使用 NGINX 的 Ingress Controller，用來處理外部 HTTP/HTTPS 流量並依規則轉送至內部服務。

| 功能 | 說明 |
| --- | --- |
| 反向代理 | 接收外部 HTTP/HTTPS 請求，依規則導向 Service / Pod |
| 負載平衡 | 在多個 Pod 之間分配流量 |
| TLS 終止 | 處理 HTTPS 憑證，在入口處完成 SSL |
| 路由管理 | 根據 Ingress 的 host / path 規則轉送請求 |

### 套用官方部署設定

```bash
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
```

### 等待 ingress-nginx 啟動

```bash
kubectl wait --namespace ingress-nginx \
  --for=condition=Ready pods \
  --selector=app.kubernetes.io/component=controller \
  --timeout=180s
```

#### 指令含義

| 指令部分 | 說明 |
| --- | --- |
| `kubectl wait` | 等待資源符合某種狀態 |
| `--namespace ingress-nginx` | 指定搜尋的 namespace |
| `--for=condition=Ready` | 等待 Pod 的 Ready 狀態 |
| `--selector=...=controller` | 限定只等待 ingress-nginx controller Pod |
| `--timeout=180s` | 最多等待 180 秒 |

### 確認啟動狀態

```bash
kubectl get pods -n ingress-nginx
```

---

## 建立 FastAPI 應用

### 專案結構

```bash
app/
├── Dockerfile
├── main.py
└── requirements.txt
```

### requirements.txt

```txt
fastapi==0.115.5
uvicorn==0.32.0
httpx==0.27.2
pytest==8.3.3
```

### main.py

```python
from fastapi import FastAPI

app = FastAPI(title="python-kind-starter", version="1.0.0")

@app.get("/")
def root():
    return {"message": "ok", "service": "python-kind-starter", "version": "1.0.0"}

@app.get("/healthz")
def healthz():
    return {"status": "healthy"}
```

### Dockerfile

```dockerfile
FROM python:3.11-slim

ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 PORT=8000

WORKDIR /app

RUN pip install --no-cache-dir --upgrade pip

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

EXPOSE 8000

CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
```

### 建置 image 並載入 kind

```bash
# 建置 Docker image
docker build -t python-kind-starter:local app

# 載入到 kind 叢集（kind 預設無法存取本機 image registry）
kind load docker-image python-kind-starter:local --name dev-cluster
```

---

## 建立 Kubernetes 資源

### namespace.yaml

```yaml
apiVersion: v1
kind: Namespace
metadata:
  name: demo
```

### service.yaml

```yaml
apiVersion: v1
kind: Service
metadata:
  name: python-api
  namespace: demo
  labels:
    app: python-api
spec:
  type: ClusterIP
  selector:
    app: python-api
  ports:
    - name: http
      port: 8000
      targetPort: 8000
```

### ingress.yaml

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: python-api
  namespace: demo
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  ingressClassName: nginx
  rules:
    - host: python.local
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: python-api
                port:
                  number: 8000
```

### deployment.yaml

```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: python-api
  namespace: demo
  labels:
    app: python-api
spec:
  replicas: 1
  selector:
    matchLabels:
      app: python-api
  template:
    metadata:
      labels:
        app: python-api
    spec:
      containers:
        - name: api
          image: python-kind-starter:local
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 8000
          readinessProbe:
            httpGet:
              path: /healthz
              port: 8000
            initialDelaySeconds: 2
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /healthz
              port: 8000
            initialDelaySeconds: 5
            periodSeconds: 10
```

### 執行部署

```bash
kubectl apply -f k8s/ -n demo
```

執行後，叢集會：
1. 建立 Namespace（若已存在則跳過）
2. 建立 Deployment → 產生並啟動 Pod
3. 建立 Service → 給 Pod 一個穩定的存取入口
4. 建立 Ingress → 將 HTTP 流量導向 Service

---

## Makefile — 常用指令整合

將常用操作包裝成 make 指令，避免每次輸入落落長的指令。

```makefile
# Makefile

CLUSTER_NAME=dev-cluster
IMAGE_NAME=python-kind-starter:local
NAMESPACE=demo

# ── 叢集管理 ────────────────────────────────
.PHONY: cluster-up
cluster-up: ## 建立 kind 叢集
	kind create cluster --name $(CLUSTER_NAME) --config kind/kind-config.yaml

.PHONY: cluster-down
cluster-down: ## 刪除 kind 叢集
	kind delete cluster --name $(CLUSTER_NAME)

# ── Ingress ──────────────────────────────────
.PHONY: ingress-install
ingress-install: ## 安裝 ingress-nginx
	kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
	kubectl wait --namespace ingress-nginx \
		--for=condition=Ready pods \
		--selector=app.kubernetes.io/component=controller \
		--timeout=180s

# ── 應用程式 ─────────────────────────────────
.PHONY: build
build: ## 建置 Docker image
	docker build -t $(IMAGE_NAME) app

.PHONY: load
load: ## 將 image 載入 kind
	kind load docker-image $(IMAGE_NAME) --name $(CLUSTER_NAME)

.PHONY: deploy
deploy: ## 部署所有 k8s 資源
	kubectl apply -f k8s/ -n $(NAMESPACE)

.PHONY: undeploy
undeploy: ## 移除所有 k8s 資源
	kubectl delete -f k8s/ -n $(NAMESPACE)

# ── 測試 ─────────────────────────────────────
.PHONY: port-forward
port-forward: ## 建立 port-forward（背景執行）
	kubectl -n $(NAMESPACE) port-forward svc/python-api 8080:8000

.PHONY: test
test: ## 測試 API 是否正常回應
	curl -s http://127.0.0.1:8080/ | jq .
	curl -s http://127.0.0.1:8080/healthz | jq .

# ── 一鍵流程 ─────────────────────────────────
.PHONY: up
up: cluster-up ingress-install build load deploy ## 一鍵啟動完整環境

.PHONY: down
down: cluster-down ## 一鍵清除環境

.PHONY: help
help: ## 顯示所有指令說明
	@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \
		awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}'
```

### 常用指令速查

| 指令 | 說明 |
| --- | --- |
| `make up` | 一鍵建立叢集、安裝 ingress、建置並部署應用 |
| `make down` | 一鍵刪除叢集，清除所有資源 |
| `make build` | 僅建置 Docker image |
| `make deploy` | 僅部署 k8s YAML |
| `make port-forward` | 建立 localhost:8080 → Service:8000 的轉發 |
| `make test` | 對 API 發出測試請求 |
| `make help` | 顯示所有可用指令 |

---

## 完整驗證 Checklist

部署完成後，依序執行以下確認步驟：

### ✅ 1. 確認 Namespace 存在
```bash
kubectl get ns demo
```
期望：`demo   Active`

### ✅ 2. 確認 Pod 正常運行
```bash
kubectl get pods -n demo
```
期望：`STATUS` 為 `Running`，`READY` 為 `1/1`

### ✅ 3. 確認 Service 存在且 Port 正確
```bash
kubectl -n demo get svc python-api -o wide
```
期望：`PORT(S)` 顯示 `8000/TCP`

### ✅ 4. 確認 Ingress 設定正確
```bash
kubectl -n demo get ingress python-api
```
期望：`HOSTS` 顯示 `python.local`

### ✅ 5. 測試 API 回應（port-forward 方式）
```bash
# 開啟 port-forward（另開一個 terminal）
kubectl -n demo port-forward svc/python-api 8080:8000

# 測試
curl -s http://127.0.0.1:8080/ | jq .
curl -s http://127.0.0.1:8080/healthz | jq .
```

期望回應：
```json
{"message": "ok", "service": "python-kind-starter", "version": "1.0.0"}
{"status": "healthy"}
```

### ✅ 6. 測試透過 Ingress 存取（domain 方式）

先在本機 `/etc/hosts` 加入以下設定：
```
127.0.0.1 python.local
```

然後測試：
```bash
curl -s http://python.local/ | jq .
```

---

## 清理環境

實驗結束後，執行以下指令清除所有資源：

```bash
# 只移除應用部署（保留叢集）
kubectl delete -f k8s/ -n demo

# 完整刪除 kind 叢集（所有資源一併清除）
kind delete cluster --name dev-cluster
```

或使用 Makefile：
```bash
make down
```

---

## 結語

透過 kind 在本地建立 Kubernetes 實驗環境，能以最輕量的方式理解叢集的運作方式。從建立 kind 叢集、安裝 ingress-nginx，到部署 FastAPI 應用，每一步都讓整個 Kubernetes 架構在本機清楚呈現。

這樣的環境易於重現、易於測試，也能讓開發者在不依賴雲端的情況下，完整掌握 Service、Ingress、Deployment 等核心概念。完成所有流程後，便擁有了一個可放心實驗、可快速迭代的本地 Kubernetes 開發基礎。
