Kubernetes API Server за 300 рядків¶
Written by:
Igor Gorovyy
DevOps Engineer Lead & Senior Solutions Architect
Kubernetes API Server - центр всього кластера. Через нього проходять всі запити: створити под, подивитись стан, видалити deployment. У Shepherd ми побудували його за ~300 рядків ключового коду (520 з хелперами і хендлерами для всіх ресурсів), використовуючи тільки стандартну бібліотеку net/http.
Структура¶
type APIServer struct {
store *Store
scheduler *Scheduler
server *http.Server
logger *log.Logger
}
API Server не містить бізнес-логіки. Він приймає HTTP запити, валідує, записує в Store і повідомляє інші компоненти.
Реєстрація маршрутів¶
func NewAPIServer(addr string, store *Store,
scheduler *Scheduler, logger *log.Logger) *APIServer {
mux := http.NewServeMux()
mux.HandleFunc("/api/v1/pods", api.handlePods)
mux.HandleFunc("/api/v1/namespaces/",
api.handleNamespacedResource)
mux.HandleFunc("/api/v1/nodes", api.handleNodes)
mux.HandleFunc("/api/v1/nodes/", api.handleNode)
mux.HandleFunc("/api/v1/services", api.handleServices)
mux.HandleFunc("/api/v1/deployments", api.handleDeployments)
mux.HandleFunc("/api/v1/events", api.handleEvents)
mux.HandleFunc("/healthz", func(w http.ResponseWriter,
r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("ok"))
})
mux.HandleFunc("/api/v1/info", api.handleInfo)
api.server = &http.Server{
Addr: addr,
Handler: api.logging(mux),
}
return api
}
URL-и схожі на справжній Kubernetes: /api/v1/pods, /api/v1/namespaces/{ns}/pods/{name}.
Namespaced ресурси¶
func (api *APIServer) handleNamespacedResource(
w http.ResponseWriter, r *http.Request) {
// /api/v1/namespaces/{ns}/pods/{name}
parts := strings.Split(
strings.TrimPrefix(r.URL.Path,
"/api/v1/namespaces/"), "/")
ns := parts[0]
resource := parts[1]
switch resource {
case "pods":
if len(parts) == 2 {
// Список подів в namespace
api.listPods(w, r, ns)
} else {
name := parts[2]
switch r.Method {
case http.MethodGet:
api.getPod(w, r, ns, name)
case http.MethodPut:
api.updatePod(w, r, ns, name)
case http.MethodDelete:
api.deletePod(w, r, ns, name)
}
}
case "services": // ...
case "deployments": // ...
}
}
Створення Pod¶
Ось де відбувається найцікавіше:
func (api *APIServer) createPod(w http.ResponseWriter,
r *http.Request) {
var pod Pod
json.NewDecoder(r.Body).Decode(&pod)
// Заповнюємо дефолти
pod.Kind = "Pod"
if pod.Metadata.Namespace == "" {
pod.Metadata.Namespace = "default"
}
if pod.Metadata.UID == "" {
pod.Metadata.UID = generateUID()
}
pod.Metadata.CreatedAt = time.Now()
pod.Status.Phase = PodPending // Завжди Pending!
api.store.CreatePod(&pod)
// Записуємо подію
api.store.RecordEvent(Event{
Type: "Normal",
Reason: "Created",
Message: fmt.Sprintf("Pod %s created",
pod.Metadata.Name),
Object: "pod/" + pod.Metadata.Name,
})
// Запускаємо планування асинхронно
go api.scheduler.SchedulePod(&pod)
respondJSON(w, http.StatusCreated, pod)
}
Дивись, яка штука: под завжди створюється як Pending. Навіть якщо є вільні ноди. Планування відбувається асинхронно через go api.scheduler.SchedulePod(). Це та сама модель, що і в справжньому Kubernetes.
Middleware для логування¶
func (api *APIServer) logging(next http.Handler) http.Handler {
return http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
api.logger.Printf("%s %s %s",
r.Method, r.URL.Path, time.Since(start))
})
}
graph LR
REQ["HTTP Request"] --> LOG["Logging middleware"]
LOG --> MUX["ServeMux routing"]
MUX --> H["Handler"]
H --> STORE["BoltDB Store"]
H --> RESP["JSON Response"]
Хелпери¶
func respondJSON(w http.ResponseWriter, status int, v any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(v)
}
func generateUID() string {
b := make([]byte, 16)
rand.Read(b)
return fmt.Sprintf("%x-%x-%x-%x-%x",
b[0:4], b[4:6], b[6:8], b[8:10], b[10:16])
}
UID генерується у форматі, схожому на UUID. Не RFC 4122 сумісний, але для ідентифікації достатньо.
Що не реалізовано¶
У справжньому Kubernetes API Server є:
- Admission webhooks (валідація і мутація)
- RBAC (авторизація)
- Watch API (long polling для змін)
- CRD (Custom Resource Definitions)
- API versioning і conversion
- etcd як бекенд (у нас BoltDB)
Але суть та сама: REST API, CRUD операції, асинхронні контролери. Цього достатньо, щоб зрозуміти архітектуру Kubernetes.
Нюанс¶
Немає валідації вхідних даних. Можна створити под без containers, deployment з від'ємним replicas, service без портів. У Kubernetes admission webhooks і вбудована валідація це ловлять. У Shepherd - garbage in, garbage out.
Спробуй сам¶
# Запусти Shepherd в standalone mode:
sudo ./shepherd --mode standalone --addr :9876
# В іншому терміналі:
curl -s localhost:9876/healthz
curl -s localhost:9876/api/v1/info | jq .
curl -s localhost:9876/api/v1/pods | jq .
API працює. Далі - BoltDB: як embedded база зберігає весь стан кластера.
Серія: Оркестратор (Shepherd = Kubernetes)¶
- Linux Namespaces | 2. Re-Exec Pattern | 3. pivot_root | 4. Cgroups v2 | 5. OverlayFS | 6. Bridge Networking | 7. NAT і iptables | 8. Image Management | 9. Container Lifecycle | 10. Docker CLI | 11. Kubernetes API Server (ця стаття)
Ресурси¶
- Kubernetes Components — загальна архітектура
- kube-apiserver reference — CLI-прапорці й поведінка
- API concepts — ресурси, списки, watches
- Kubernetes architecture — control plane і node-компоненти
- net/http package — стандартний HTTP-сервер Go
- kube-apiserver source code — реальна імплементація API server Kubernetes
- Admission Controllers — webhooks для валідації та мутації
Вихідний код циклу: github.com/igorgorovoy/sheep-shepherd-meadow
Попередня: Docker CLI
