從 Kind 到 K3s: Local Registry 與 Production 部署流程

DevOps 學習筆記

用 kind 學完 K8s 核心概念後, 換成 k3s 並搭配 local registry, 讓本機練習更貼近 production 的部署流程。同時釐清 K8s 網路隔離機制, 以及 production 不會用 kubectl set image 的原因

為什麼從 kind 換成 k3s

kind 學概念夠用, 但有幾個跟 production 差很遠的地方:

功能 kind k3s Production (GKE/AKS)
Ingress controller 沒有, 要自己裝 內建 Traefik 內建 cloud LB
Service LoadBalancer 拿不到外部 IP 內建 ServiceLB, 拿得到 IP 真的外部 IP
多 Node 可以但很少人用 輕鬆開 multi-node 自動管理
Local storage 要自己設 內建 local-path provisioner 自動 (Persistent Disk)
Image 載入 kind load (非標準) 從 registry pull (標準流程) 從 Artifact Registry pull

k3s 是完整的 K8s, 只是更輕量。裝完就有 Ingress 和 LoadBalancer 可以用, 不需要 port-forward

安裝與設定

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# install k3s
curl -sfL https://get.k3s.io | sh -

# k3s kubeconfig is at /etc/rancher/k3s/k3s.yaml (root-only)
# copy to kubectl's default location
mkdir -p ~/.kube
sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
sudo chown $(id -u):$(id -g) ~/.kube/config

# verify
kubectl get nodes

kind 安裝完會自動設定 kubeconfig, 但 k3s 把 kubeconfig 放在 root-only 的路徑, 需要手動複製到 ~/.kube/config, 讓一般使用者的 kubectl 能讀到

Local Registry — 模擬 Production 的 Image 流程

kind 的做法 (非標準)

1
2
# kind: transfer image from Docker daemon to kind's containerd
kind load docker-image go-api:0.0.2 --name devops-lab

這是 kind 專屬的指令, production 環境不存在。實際上 K8s 是從 registry pull image 的

k3s + Local Registry (標準流程)

跑一個 local registry container, 跟 Docker Hub / GHCR / Artifact Registry 是同一種東西, 只是在本機:

1
2
3
4
# start local registry
# --name registry is the container name (can be anything)
# registry:2 is the image name + tag from Docker Hub
docker run -d -p 5050:5000 --restart always --name registry registry:2

注意 port mapping: registry 內部固定聽 5000, 對外可以用任意 port。5050:5000 代表 host 的 5050 → container 的 5000

Build → Push → Pull 流程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 1. build, tag points to local registry
docker buildx build -t 127.0.0.1:5050/go-api:0.0.3 .

# 2. push to registry (same as pushing to GHCR or Artifact Registry)
docker push 127.0.0.1:5050/go-api:0.0.3

# 3. verify image exists in registry
curl http://127.0.0.1:5050/v2/_catalog
# {"repositories":["go-api"]}

curl http://127.0.0.1:5050/v2/go-api/tags/list
# {"name":"go-api","tags":["0.0.3"]}

跟 production 的對比:

1
2
3
Local:  docker push 127.0.0.1:5050/go-api:0.0.3
GHCR:   docker push ghcr.io/ianyu93/pebble-api:latest
GCP:    docker push us-docker.pkg.dev/my-project/repo/api:abc123

只有目的地不同, 流程一模一樣。K8s 看到 Deployment 裡的 image: 127.0.0.1:5050/go-api:0.0.3, 會自己去 registry pull

Deployment YAML

1
2
3
4
5
6
spec:
  containers:
    - name: go-api
      image: 127.0.0.1:5050/go-api:0.0.3   # pull from registry, not local import
      ports:
        - containerPort: 8080

K8s 網路隔離

為什麼本機連不到 Service

K8s cluster 有自己獨立的網路, 跟 host 網路是隔離的:

1
2
3
4
5
6
7
8
9
Host network (your machine)
├── can reach: 127.0.0.1, localhost
├── can reach: docker containers (via port mapping)
└── cannot reach: K8s Pod IP, Service IP, Service DNS name

K8s cluster internal network (isolated)
├── Pod IP: 10.42.0.x
├── Service IP: 10.43.x.x
└── DNS: go-api → 10.43.148.136

go-api 這個 DNS 名字只有 K8s 內部的 CoreDNS 認識, 本機的 DNS 不知道它是什麼

從 cluster 內部驗證 Service Discovery

1
2
3
4
5
6
# create a temporary Pod inside the cluster
kubectl run test --rm -it --image=busybox -- sh

# inside the Pod, call go-api by name
wget -qO- http://go-api:8080/healthz
# {"status":"ok","version":"0.0.3"}

kubectl run --rm -it 的參數:

  • --rm: remove Pod when done (like docker run –rm)
  • -i: interactive, connect stdin
  • -t: allocate tty
  • -- sh: run shell inside the Pod

在 Pod 裡面用 http://go-api:8080 能連到, 是因為 Pod 在 K8s 網路裡, 可以查 CoreDNS

Production 怎麼讓外部連進來

本機連不到 cluster 內部, 那外部使用者怎麼連?

方式 用途
kubectl port-forward 開發測試用, 不是 production 做法
Service type: LoadBalancer k3s / cloud 環境, 拿到真的外部 IP
Ingress production 標準做法, 一個入口路由到多個 Service

kind 拿不到 LoadBalancer IP, 只能用 port-forward。k3s 內建 ServiceLB, 改成 type: LoadBalancer 就能拿到外部 IP

Production 部署流程 vs 練習捷徑

練習時的捷徑 (不要帶到 production)

1
2
# imperative: directly change image, no record
kubectl set image deployment/go-api go-api=go-api:0.0.4

Production 的做法

改 YAML → commit → push → CI/CD 自動 apply:

1
2
3
4
5
# k8s/deployment.yaml
spec:
  containers:
    - name: go-api
      image: 127.0.0.1:5050/go-api:0.0.4   # change this line
1
2
3
4
5
# the standard flow
git add k8s/deployment.yaml
git commit -m "bump go-api to 0.0.4"
git push
# CI/CD pipeline runs kubectl apply automatically

為什麼不用 kubectl set image

kubectl set image YAML + git push
紀錄 無, 誰改的都不知道 git history 完整追蹤
回滾 kubectl rollout undo git revert (更可靠)
審核 無法 review PR review 後才 merge
一致性 cluster 狀態跟 YAML 不一致 git = single source of truth

這就是 GitOps 的核心 — git 裡的 YAML 永遠代表 production 的真實狀態。ArgoCD 這類工具就是監控 git repo, YAML 一變就自動 sync 到 cluster

從 Docker Compose 到 K8s 的對應

如果已經有跑在 EC2 + Docker Compose 的服務, 轉 K8s 時的對應關係:

Docker Compose K8s
services: each service Deployment + Service YAML
ports: Service (ClusterIP / LoadBalancer)
environment: ConfigMap / Secret
volumes: PersistentVolumeClaim (PVC)
depends_on: K8s 不管啟動順序, app 要自己 retry
logging: awslogs GKE 內建送 Cloud Logging
restart: always Deployment 預設行為 (reconciliation loop)
nginx reverse proxy Ingress controller 取代

轉換時 application code 和 Dockerfile 完全不用改, 改的只是部署方式 — 從 Compose 換成 K8s YAML

References

0%