Namespace: jumptotech
│
├── ConfigMap ───────────────┐
│ │
├── Secret ──────────────────┤
│ ↓
│ Deployment
│ ↓
│ ReplicaSet
│ ↓
│ ┌────────────┼────────────┐
│ ↓ ↓ ↓
│ Pod 1 Pod 2 Pod 3
│ │ │ │
│ └────────────┼────────────┘
│ ↑
│ Service
│ ↑
│ Ingress
│
├── ServiceAccount ─────→ Pods
│
└── PDB ────────────────→ protects availability
1. Создайте папку
mkdir k8s-core-lab
cd k8s-core-lab
В конце у вас будет:
k8s-core-lab/
├── namespace.yaml
├── configmap.yaml
├── secret.yaml
├── serviceaccount.yaml
├── deployment.yaml
├── service.yaml
├── pdb.yaml
└── ingress.yaml
2. Namespace
Создайте namespace.yaml:
apiVersion: v1
kind: Namespace
metadata:
name: jumptotech
Применяем:
kubectl apply -f namespace.yaml
kubectl get namespaces
Объяснение:
Namespace — логическое разделение ресурсов внутри одного Kubernetes cluster.
Теперь почти всё приложение будем создавать внутри:
jumptotech
3. ConfigMap
configmap.yaml:
apiVersion: v1
kind: ConfigMap
metadata:
name: web-config
namespace: jumptotech
data:
APP_NAME: "JumpToTech"
APP_ENV: "production"
APP_MESSAGE: "Welcome to Kubernetes"
Применяем:
kubectl apply -f configmap.yaml
kubectl get configmap -n jumptotech
kubectl describe configmap web-config -n jumptotech
Объясните:
ConfigMap хранит несекретную конфигурацию отдельно от Docker image.
Например:
APP_ENV
APP_NAME
LOG_LEVEL
API_URL
Не надо rebuild image только потому, что:
APP_ENV=dev
нужно поменять на:
APP_ENV=production
4. Secret
secret.yaml:
apiVersion: v1
kind: Secret
metadata:
name: web-secret
namespace: jumptotech
type: Opaque
stringData:
DB_USERNAME: "admin"
DB_PASSWORD: "jumptotech123"
Для учебного lab это удобно, но объясните студентам:
В production реальные passwords нельзя коммитить в Git в таком YAML. Обычно используют внешний secret-management solution и соответствующую интеграцию.
Применяем:
kubectl apply -f secret.yaml
kubectl get secrets -n jumptotech
kubectl describe secret web-secret -n jumptotech
Обратите внимание: describe не должен просто печатать secret value.
5. ServiceAccount
Создаём serviceaccount.yaml:
apiVersion: v1
kind: ServiceAccount
metadata:
name: web-service-account
namespace: jumptotech
Применяем:
kubectl apply -f serviceaccount.yaml
kubectl get serviceaccounts -n jumptotech
Объяснение:
ServiceAccount — identity для workload/Pod внутри Kubernetes.
Очень важно:
Human
↓
User identity
Pod
↓
ServiceAccount
Сам по себе ServiceAccount не означает, что Pod автоматически получил доступ ко всему. Permissions определяются отдельно, например RBAC; в AWS также могут использоваться workload identity mechanisms.
6. Deployment + Requests + Limits
Теперь основной файл — deployment.yaml.
Замените YOUR_ECR_IMAGE своим настоящим ECR URI.
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-deployment
namespace: jumptotech
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
serviceAccountName: web-service-account
containers:
- name: web-container
image: YOUR_ECR_IMAGE
ports:
- containerPort: 80
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
env:
- name: APP_NAME
valueFrom:
configMapKeyRef:
name: web-config
key: APP_NAME
- name: APP_ENV
valueFrom:
configMapKeyRef:
name: web-config
key: APP_ENV
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: web-secret
key: DB_USERNAME
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: web-secret
key: DB_PASSWORD
Здесь остановитесь надолго.
Студенты должны понимать:
Deployment
↓
replicas: 3
↓
ReplicaSet
↓
3 Pods
И:
resources:
requests:
cpu: "100m"
memory: "128Mi"
означает:
Scheduler учитывает эти requests при выборе Node.
А:
limits:
cpu: "500m"
memory: "256Mi"
устанавливает верхние ограничения для контейнера.
Ключевая разница:
REQUEST
↓
Scheduler / placement
LIMIT
↓
Runtime resource ceiling
CPU:
1000m = 1 CPU
500m = 0.5 CPU
100m = 0.1 CPU
7. Apply Deployment
kubectl apply -f deployment.yaml
Проверяем:
kubectl get deployments -n jumptotech
kubectl get rs -n jumptotech
kubectl get pods -n jumptotech
kubectl get pods -n jumptotech -o wide
Должно быть:
Deployment
↓
ReplicaSet
↓
3 Pods
8. Проверяем Resources
Выберите Pod:
kubectl describe pod POD_NAME -n jumptotech
Найдите:
Limits:
cpu: 500m
memory: 256Mi
Requests:
cpu: 100m
memory: 128Mi
Спросите:
Что использует Scheduler при выборе Node?
Requests.
Что будет, если приложение пытается использовать CPU больше CPU limit?
CPU может throttled.
Что может произойти при превышении memory limit?
Контейнер может быть убит из-за OOM (OOMKilled).
9. Проверяем ConfigMap и Secret внутри Pod
Сначала ConfigMap:
kubectl exec -n jumptotech POD_NAME -- printenv APP_NAME
Должно показать:
JumpToTech
Потом:
kubectl exec -n jumptotech POD_NAME -- printenv APP_ENV
Получим:
production
Для учебной демонстрации можно проверить username:
kubectl exec -n jumptotech POD_NAME -- printenv DB_USERNAME
Главная идея:
ConfigMap ──────┐
↓
Pod
↑
Secret ─────────┘
10. Проверяем ServiceAccount
kubectl get pod POD_NAME \
-n jumptotech \
-o jsonpath='{.spec.serviceAccountName}'
Получим:
web-service-account
Связь:
Deployment Pod Template
↓
serviceAccountName
↓
web-service-account
↓
Pod identity
11. Service
Создаём service.yaml:
apiVersion: v1
kind: Service
metadata:
name: web-service
namespace: jumptotech
spec:
type: ClusterIP
selector:
app: web
ports:
- protocol: TCP
port: 80
targetPort: 80
Применяем:
kubectl apply -f service.yaml
kubectl get svc -n jumptotech
Главная связь:
Service
selector: app=web
↓
MATCH
↓
Pod labels
app=web
12. EndpointSlice
Сначала:
kubectl get pods -n jumptotech -o wide
Посмотрите Pod IP.
Потом:
kubectl get endpointslice \
-n jumptotech \
-l kubernetes.io/service-name=web-service
Для дополнительной учебной проверки можно также использовать старую команду:
kubectl get endpoints web-service -n jumptotech
но в вашем кластере вы уже видели предупреждение о deprecated Endpoints API, поэтому EndpointSlice — современный объект, который студентам лучше знать.
Логика:
Service selector
↓
Pod labels
↓
matching backend Pods
↓
EndpointSlice maintains backend addresses
13. Проверяем Service
Пока ALB не нужен.
kubectl port-forward \
service/web-service \
8080:80 \
-n jumptotech
Открываем:
http://localhost:8080
Если приложение открывается:
Deployment ✅
Pods ✅
Service ✅
14. PodDisruptionBudget — PDB
Теперь очень хороший production object.
Создаём pdb.yaml:
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: web-pdb
namespace: jumptotech
spec:
minAvailable: 2
selector:
matchLabels:
app: web
Применяем:
kubectl apply -f pdb.yaml
kubectl get pdb -n jumptotech
Объясните:
У нас:
replicas = 3
PDB:
minAvailable = 2
То есть при voluntary disruptions, например некоторых операциях drain/maintenance, Kubernetes должен учитывать требование:
3 Pods
Pod 1 Pod 2 Pod 3
✓ ✓ ✓
PDB:
минимум 2 должны оставаться available
Очень важное уточнение:
PDB не гарантирует, что Pod никогда не упадёт.
Если Node внезапно умер или приложение crash — PDB не является магической защитой от этого.
PDB в первую очередь ограничивает voluntary disruptions.
15. Readiness и Liveness Probes
Я бы обязательно добавила их в этот же Deployment lab.
В deployment.yaml под container добавьте:
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 5
Получится:
containers:
- name: web-container
image: YOUR_ECR_IMAGE
ports:
- containerPort: 80
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 5
Объясните:
LIVENESS
"Приложение живое?"
↓
если постоянно fail
↓
kubelet может restart container
READINESS
"Приложение готово получать traffic?"
↓
NO
↓
Pod не должен использоваться
как ready backend
Применяем:
kubectl apply -f deployment.yaml
kubectl get pods -n jumptotech
16. Ingress
Если AWS Load Balancer Controller уже установлен, создаём ingress.yaml:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web-ingress
namespace: jumptotech
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
spec:
ingressClassName: alb
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-service
port:
number: 80
Перед применением:
kubectl get deployment \
-n kube-system \
aws-load-balancer-controller
Если controller существует:
kubectl apply -f ingress.yaml
Проверяем:
kubectl get ingress -n jumptotech
kubectl describe ingress web-ingress -n jumptotech
17. Теперь вся архитектура
На доске нарисуйте:
INTERNET
↓
ALB
↓
Ingress
↓
web-service
selector: app=web
↓
┌───────────┼───────────┐
↓ ↓ ↓
Pod 1 Pod 2 Pod 3
app=web app=web app=web
│ │ │
└───────────┼───────────┘
↑
Deployment
↑
ReplicaSet
ConfigMap ───────────────→ Pods
Secret ─────────────────→ Pods
ServiceAccount ─────────→ Pods
Resources ──────────────→ Containers
PDB ────────────────────→ availability during
voluntary disruption
EVERYTHING:
Namespace = jumptotech
18. Очень важный troubleshooting lab
Теперь специально ломаем.
Поменяйте Service:
selector:
app: WRONG
Примените:
kubectl apply -f service.yaml
Проверьте:
kubectl get svc -n jumptotech
kubectl get pods -n jumptotech
kubectl get endpointslice \
-n jumptotech \
-l kubernetes.io/service-name=web-service
Pods:
Running
Running
Running
Service:
Exists
Но backend endpoints не соответствуют нужным Pods.
Почему?
Service selector
app=WRONG
X
Pod labels
app=web
Исправляем:
selector:
app: web
И:
kubectl apply -f service.yaml
19. Resources troubleshooting
Теперь специально создайте слишком большой request:
resources:
requests:
cpu: "100"
memory: "100Gi"
Это означает 100 CPU cores, а не 100m.
Примените:
kubectl apply -f deployment.yaml
Новые Pods могут зависнуть:
Pending
Тогда:
kubectl describe pod POD_NAME -n jumptotech
Ищите scheduling events вроде:
Insufficient cpu
Insufficient memory
Объяснение:
Pod
requests 100 CPU
↓
Scheduler
↓
Node 1 ❌
Node 2 ❌
↓
No suitable Node
↓
Pending
После демонстрации обязательно верните:
requests:
cpu: "100m"
memory: "128Mi"
20. Проверяем self-healing
kubectl get pods -n jumptotech
Удалите один Pod:
kubectl delete pod POD_NAME -n jumptotech
Смотрите:
kubectl get pods -n jumptotech -w
Объясняем:
Deployment desired replicas = 3
↓
ReplicaSet
↓
Pod deleted
↓
осталось 2
↓
replacement Pod
↓
снова 3
После появления replacement Pod сравните:
kubectl get pods -n jumptotech -o wide
с:
kubectl get endpointslice \
-n jumptotech \
-l kubernetes.io/service-name=web-service
Новый Pod может иметь новый IP, но поскольку:
label = app=web
Service продолжает выбирать его.
Что студент обязан знать после этого Lab
Я бы дала им именно эти объекты как Kubernetes Core Pack:
Namespace
↓
где логически находятся ресурсы
ConfigMap
↓
non-sensitive configuration
Secret
↓
sensitive configuration
ServiceAccount
↓
workload identity
Deployment
↓
desired application state
ReplicaSet
↓
maintains replica count
Pod
↓
smallest deployable workload unit
Container
↓
running application
Requests
↓
scheduler capacity decision
Limits
↓
resource ceiling
Liveness Probe
↓
"жив ли container/application?"
Readiness Probe
↓
"готов ли Pod получать traffic?"
Service
↓
stable service abstraction + selector
EndpointSlice
↓
backend addresses for Service
PDB
↓
protects availability during
voluntary disruptions
Ingress
↓
HTTP/HTTPS routing configuration
Ingress Controller
↓
implements/reconciles that configuration
И главное: студенты должны не просто запомнить YAML, а уметь рассказать цепочку:
Image in ECR
↓
Deployment
↓
ReplicaSet
↓
Pod
↓
Container
Scheduler → chooses Node using requests
Kubelet → manages assigned Pod/containers
ConfigMap + Secret → configuration
ServiceAccount → workload identity
Service selector
↓
Pod labels
↓
EndpointSlice
↓
backend Pods
Ingress
↓
AWS Load Balancer Controller
↓
ALB
↓
Service/backend
Top comments (0)