Первые приложения

Кластер поднят, сеть и хранилище понятны — пора запустить что-то полезное. На этой странице три практических упражнения: классический nginx «по учебнику», Kubernetes Dashboard для наглядности и собственное приложение — от Dockerfile до деплоя.

Содержание

Разворачиваем nginx: Deployment + Service

Классическая связка любого приложения в k8s: Deployment описывает, что и сколько копий запускать (и следит, чтобы они жили), Service даёт им стабильный адрес. Оба описываются одним YAML-файлом.

Шаг 1. Создайте манифест

Файл nginx-demo.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-demo
  labels:
    app: nginx-demo
spec:
  replicas: 2          # две копии: одна упадёт — вторая продолжит
  selector:
    matchLabels:
      app: nginx-demo
  template:
    metadata:
      labels:
        app: nginx-demo
    spec:
      containers:
        - name: nginx
          image: nginx:1.27
          ports:
            - containerPort: 80
          resources:
            requests:
              memory: "64Mi"
              cpu: "50m"
            limits:
              memory: "128Mi"
              cpu: "200m"
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-demo
spec:
  type: NodePort
  selector:
    app: nginx-demo
  ports:
    - port: 80
      targetPort: 80
      nodePort: 30081

Обратите внимание на блок resources: requests — сколько гарантированно выделить поду, limits — потолок. Привычка всегда указывать их — лучшая прививка против «съедания» всей памяти узла одним приложением (подробности в «Советах»).

Шаг 2. Примените и проверьте

kubectl apply -f nginx-demo.yaml

# что создалось
kubectl get deployments
kubectl get pods -o wide
kubectl get svc nginx-demo

Когда оба пода перейдут в Running, откройте в браузере http://192.168.1.50:30081 (IP любого узла) — должна появиться страница «Welcome to nginx!».

Шаг 3. Поиграйте с отказоустойчивостью

# удалите один из подов
kubectl delete pod -l app=nginx-demo --field-selector=status.phase=Running

# и сразу смотрите: deployment уже создаёт замену
kubectl get pods -w

Это и есть суть Kubernetes: вы описываете желаемое состояние («хочу 2 копии»), а контроллер постоянно приводит реальность к описанию.

Шаг 4. Обновите версию без простоя

# сменить образ на новую версию
kubectl set image deployment/nginx-demo nginx=nginx:1.28

# смотреть rolling update вживую
kubectl rollout status deployment/nginx-demo

# история обновлений
kubectl rollout history deployment/nginx-demo

# откат, если что-то пошло не так
kubectl rollout undo deployment/nginx-demo

Rolling update поднимает новые поды и гасит старые по одному — с точки зрения посетителя сайт даже не моргнул.

Конфиги и секреты: ConfigMap и Secret

Настройки приложения не принято зашивать в образ. Для них есть два объекта:

Пример: передаём приложению строку приветствия через ConfigMap и пароль через Secret:

# создать из командной строки
kubectl create configmap app-config --from-literal=greeting="Привет из кластера!"
kubectl create secret generic app-secret --from-literal=db-password="SuperSecret"

# посмотреть
kubectl get configmap app-config -o yaml
kubectl get secret app-secret

Подключение в под — как переменные окружения:

      env:
        - name: GREETING
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: greeting
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: app-secret
              key: db-password
Не коммитьте секреты в git YAML с Secret в открытом виде — это пароль в репозитории. Для серьёзной работы есть Sealed Secrets и внешние хранилища, но на старте достаточно правила: файлы с Secret не попадают в git (добавьте в .gitignore).

Kubernetes Dashboard

Dashboard — официальный веб-интерфейс кластера: узлы, поды, логи, события, всё наглядно. Для новичка это отличный способ «увидеть» кластер, пока kubectl ещё не стал привычкой.

Шаг 5. Установите Dashboard

kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.7.0/aio/deploy/recommended.yaml

# проверить
kubectl get pods -n kubernetes-dashboard

Шаг 6. Создайте пользователя с доступом

Dashboard не имеет своей системы пользователей — вход по токену ServiceAccount. Создадим аккаунт администратора (файл dashboard-admin.yaml):

apiVersion: v1
kind: ServiceAccount
metadata:
  name: admin-user
  namespace: kubernetes-dashboard
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: admin-user
subjects:
  - kind: ServiceAccount
    name: admin-user
    namespace: kubernetes-dashboard
roleRef:
  kind: ClusterRole
  name: cluster-admin
  apiGroup: rbac.authorization.k8s.io
kubectl apply -f dashboard-admin.yaml
Про права cluster-admin — полные права на кластер. Для домашнего использования это нормально (вы и есть администратор), но понимайте: любой, кто получит токен, получает ключи от всего. Не выставляйте Dashboard в интернет без необходимости.

Шаг 7. Получите токен

# токен с длительным сроком (например, год = 8760h)
kubectl -n kubernetes-dashboard create token admin-user --duration=8760h

Команда выведет длинную строку — это и есть пароль для входа. Сохраните её в менеджере паролей.

Шаг 8. Откройте Dashboard

Самый простой способ — проброс портов через kubectl с вашего рабочего компьютера:

kubectl port-forward -n kubernetes-dashboard svc/kubernetes-dashboard 8443:443

Откройте https://localhost:8443 (браузер ругнётся на самоподписанный сертификат — это нормально, принимаем), вставьте токен — и вы внутри.

Хочется постоянного доступа Можно открыть Dashboard через Ingress, как любой другой сервис (см. «Сеть»), обязательно с HTTPS. Но для начала port-forward безопаснее: доступен только с вашей машины и только пока запущена команда.

Своё приложение: Dockerfile → образ → манифест

Теперь самое интересное: задеплоим не чужой nginx, а собственное приложение. Для примера — микроскопический веб-сервер на Python, который отвечает «Привет из кластера!».

Шаг 9. Напишите приложение и Dockerfile

Два файла в одном каталоге. Сначала app.py:

from http.server import HTTPServer, BaseHTTPRequestHandler

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.end_headers()
        self.wfile.write("Привет из кластера!".encode("utf-8"))

HTTPServer(("0.0.0.0", 8080), Handler).serve_forever()

И Dockerfile:

FROM python:3.12-slim
WORKDIR /app
COPY app.py .
EXPOSE 8080
CMD ["python", "app.py"]

Шаг 10. Соберите образ

Если Docker установлен на вашем компьютере:

# сборка образа с тегом
docker build -t myapp:1.0 .

# проверка локально
docker run --rm -p 8080:8080 myapp:1.0
# откройте http://localhost:8080
Если Docker не установлен В кластере на containerd образы можно собирать прямо на сервере с помощью buildkit или docker.io из репозитория, но проще всего поставить Docker Desktop (Windows/macOS) или docker.io (sudo apt install docker.io в Linux) на рабочую машину. Сам кластер для сборки Docker не требует.

Шаг 11. Доставьте образ в кластер

Кластер должен откуда-то взять ваш образ. Три варианта:

Вариант А — через containerd на узле (просто для одного узла): сохраните образ в файл и импортируйте на сервере:

# на машине со сборкой
docker save myapp:1.0 | gzip > myapp.tar.gz
scp myapp.tar.gz ivan@192.168.1.50:~

# на сервере (k3s/containerd): импорт в рантайм k3s
sudo k3s ctr images import myapp.tar.gz

Вариант Б — через публичный реестр: залейте образ в Docker Hub (или GitHub Container Registry):

docker tag myapp:1.0 ваш_логин/myapp:1.0
docker push ваш_логин/myapp:1.0

Вариант В — свой реестр: позже можно поднять registry прямо в кластере — но это уже продвинутая тема.

Шаг 12. Напишите манифест и задеплойте

Файл myapp.yaml (для варианта А используем локальный образ с imagePullPolicy: IfNotPresent — иначе кластер попытается скачать его из интернета):

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
        - name: myapp
          image: myapp:1.0
          imagePullPolicy: IfNotPresent   # образ уже на узле, не ходить в интернет
          ports:
            - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: myapp
spec:
  type: NodePort
  selector:
    app: myapp
  ports:
    - port: 80
      targetPort: 8080
      nodePort: 30082
kubectl apply -f myapp.yaml
kubectl get pods -l app=myapp
curl http://192.168.1.50:30082
# Привет из кластера!

Шаг 13. Посмотрите логи и зайдите внутрь

Две команды, которыми вы будете пользоваться каждый день:

# логи приложения
kubectl logs -l app=myapp

# shell внутри контейнера (для отладки)
kubectl exec -it deploy/myapp -- /bin/sh

Уборка: как удалить приложение

Удаление — такая же важная операция, как установка. Два способа:

# удалить всё, что создано манифестом
kubectl delete -f myapp.yaml

# или по именам объектов
kubectl delete deployment myapp
kubectl delete service myapp

# PVC удаляется отдельно (данные не пропадут сами)
kubectl delete pvc app-data

Если приложение ставилось в отдельный namespace — всё сносится одной командой: kubectl delete namespace myapp-ns. Удобно для экспериментов.

Что дальше Вы умеете деплоить приложения. Посмотрите страницу «Примеры» — там разбор четырёх классических домашних сервисов: Jellyfin, Nextcloud, Home Assistant и Gitea. А когда что-то сломается (сломается обязательно — и это нормально), приходите в «Советы».