Первые шаги в Kubernetes для разработчиков веб-приложений

Когда впервые смотришь на Kubernetes, кажется, что это инструмент для DevOps-инженеров с бородой и сертификатами CKA. На самом деле, для веб-разработчика он решает очень конкретные задачи: держать сервис на плаву при падении ноды, выкатывать новую версию без даунтайма и не молиться на единственный сервер в пятницу вечером. Я сам начинал с одного пода и сервиса — и это нормально. Не нужно сразу понимать все компоненты платформы. Достаточно освоить базовую модель, несколько ключевых объектов и типовой рабочий процесс.

Если объяснять совсем просто, Kubernetes — это система, которая управляет контейнерами и следит, чтобы приложение работало так, как вы задумали. Но чтобы начать пользоваться им без боли, не нужно сразу понимать все компоненты платформы. Достаточно освоить базовую модель, несколько ключевых объектов и типовой рабочий процесс.

Что такое Kubernetes и зачем он нужен разработчику

Контейнер — это удобная упаковка приложения со всеми зависимостями. Kubernetes нужен, когда одного контейнера уже мало: приложение состоит из нескольких сервисов, нужно масштабирование, высокая доступность, обновления без простоя и контроль над сетью между компонентами. На практике часто начинают с docker-compose, но быстро упираются в ограничения: нет автоматического перезапуска на другой ноде, нет декларативного управления состоянием, а ручное масштабирование превращается в боль. Kubernetes берёт на себя именно оркестрацию: вы описываете желаемое состояние, а он его поддерживает.

Для веб-приложений Kubernetes особенно полезен в таких сценариях:

  • несколько окружений: dev, stage, production — и нужно, чтобы они вели себя одинаково;
  • микросервисная архитектура, где каждый сервис живёт своей жизнью;
  • нерегулярная нагрузка и пики трафика — горизонтальное масштабирование подами по CPU или кастомным метрикам;
  • частые релизы — rolling update без остановки сервиса;
  • необходимость быстро пересоздавать экземпляры приложения после сбоя — контроллеры сами восстановят нужное количество реплик;
  • одинаковое поведение приложения на ноутбуке, в тесте и в проде — образ один и тот же, переменные окружения задаются снаружи.

Что Kubernetes не делает

Важно сразу снять завышенные ожидания. Kubernetes не ускоряет код сам по себе, не лечит плохую архитектуру и не заменяет нормальный CI/CD. Если приложение нестабильно внутри контейнера, оркестратор просто будет стабильно его перезапускать. Я не раз видел, как команда ждала, что после переезда в Kubernetes исчезнут утечки памяти или повысится производительность базы данных. Увы, оркестратор лишь предоставляет среду, а стабильность приложения — по-прежнему зона ответственности разработчика.

Базовая модель Kubernetes без лишней теории

Чтобы не утонуть в терминах, достаточно запомнить несколько сущностей. За каждым объектом стоит конкретная задача, и на старте не нужно лезть в дебри Custom Resource Definitions или операторов.

Объект Простое объяснение Для чего нужен
Pod Минимальная единица запуска Внутри живёт один или несколько контейнеров с общим сетевым пространством. Это важно для sidecar-паттернов, например, сбора логов.
Deployment Описание того, сколько и каких Pod должно быть Управляет версиями и обновлениями через ReplicaSet. Если удалить под, Deployment создаст новый.
Service Стабильный адрес для доступа к Pod Даёт сетевую точку входа — виртуальный IP и правила iptables/ipvs, которые балансируют трафик на поды по меткам.
ConfigMap Конфигурация без секретов Хранит параметры приложения: переменные окружения, конфигурационные файлы.
Secret Конфигурация с чувствительными данными Хранит пароли, токены, ключи. В идеале монтируется как том, чтобы не светить в env.
Ingress Входящий HTTP/HTTPS-трафик Публикует приложение наружу через L7-балансировщик, требует Ingress Controller (nginx, traefik).
Namespace Логическое разделение кластера Удобно для окружений и команд, изолирует ресурсы и имена.

Как это связано с веб-приложением

Типичный сценарий выглядит так:

  1. Вы собираете Docker-образ и пушите его в registry (образ должен быть доступен кластеру).
  2. Kubernetes создаёт нужное число Pod с этим образом через Deployment.
  3. Service даёт стабильный адрес для доступа, балансируя запросы между подами.
  4. Ingress публикует приложение в интернет или во внутреннюю сеть, маршрутизируя по доменам и путям.
  5. Deployment следит за обновлением версий и восстановлением при сбоях.

На практике часто путают Service и Ingress: Service даёт внутренний доступ (ClusterIP), а для внешнего HTTP нужен именно Ingress с контроллером. Если вы просто создадите Service типа LoadBalancer в облаке, он тоже даст внешний IP, но это менее гибко для маршрутизации.

С чего начать, если вы уже умеете Docker

Если вы разрабатываете веб-приложения и уже используете Docker, старт будет заметно проще. В Kubernetes меняется не сама идея контейнеризации, а способ управления контейнерами. Опыт с Docker Compose помогает понять, как описывать сервисы, но в Kubernetes декларативный YAML и нет привычного depends_on — вместо этого используют initContainers или probes для определения готовности зависимостей.

Минимальный путь входа

  1. Научитесь собирать Docker-образ без привязки к локальной машине: multi-stage сборки, аргументы сборки, никаких «у меня на машине работает».
  2. Поймите, как описывать приложение в YAML: отступы, структура, обязательные поля.
  3. Разберитесь, чем Pod отличается от Deployment: Pod — это экземпляр, Deployment — контроллер, который управляет жизненным циклом подов.
  4. Освойте Service и Ingress: как обеспечить связность и внешний доступ.
  5. Попробуйте запустить простое приложение в локальном кластере и «пощупайте» его через kubectl.
  6. Научитесь смотреть логи, события и состояние ресурсов — это 80% отладки.

Что стоит изучить в первую очередь

  • kubectl get, describe, logs, apply, delete — базовые операции;
  • kubectl explain — встроенная справка по полям ресурсов, спасает без постоянного гугления;
  • Deployment и ReplicaSet — как связаны и зачем нужны;
  • Service типов ClusterIP, NodePort, LoadBalancer — понимание, когда что применять;
  • Ingress и Ingress Controller — обязательная связка для HTTP-трафика;
  • переменные окружения через ConfigMap и Secret — инжект конфигурации без пересборки образа;
  • health checks: readiness и liveness probes — без них деплой будет нестабильным;
  • kubectl port-forward — быстрый доступ к поду без настройки Ingress, удобно для отладки.

Локальный старт: где тренироваться

Для первых шагов не нужен продакшн-кластер. Достаточно локального окружения, которое можно поднять за пару минут и без страха сломать. Я предпочитаю kind для тестирования манифестов — он быстро создаёт и удаляет кластеры в Docker, что идеально для CI и экспериментов.

Подходящие варианты

  • minikube — удобен для изучения и тестов на одной машине, требует гипервизор, много примеров в документации;
  • kind — поднимает Kubernetes-кластер внутри Docker, хорош для CI и экспериментов, близок к реальному кластеру по поведению;
  • k3d — облегчённый путь через k3s в Docker, экономит ресурсы, быстро стартует.

Что выбрать

Сценарий Что выбрать Почему
Просто понять основы minikube Много примеров, легко стартовать, есть аддоны.
Проверять манифесты и интеграции kind Быстро поднимается, близок к CI, можно создавать multi-node кластеры.
Лёгкий и компактный локальный кластер k3d Экономит ресурсы и удобен для тестов на ноутбуке с ограниченной памятью.

Для первых шагов неважно, какой вариант вы выберете. Важно, чтобы вы могли быстро поднимать, ломать и пересоздавать окружение. Я обычно советую kind: одна команда kind create cluster — и через 30 секунд у вас готовый кластер.

Первый практический сценарий: разворачиваем простое веб-приложение

Лучше всего учиться на небольшом сервисе: API на Node.js, Python, Go или любом другом знакомом стеке. Цель не в том, чтобы написать код, а в том, чтобы увидеть, как приложение живёт в Kubernetes. Я для демонстраций часто беру простой Express.js, который возвращает «Hello» и имеет health endpoint — этого достаточно, чтобы прочувствовать все этапы.

Пошаговый план

1. Соберите контейнерный образ

Образ должен быть воспроизводимым. Не полагайтесь на локальные зависимости и ручные действия. Используйте multi-stage сборки, чтобы итоговый образ был минимальным и не содержал инструментов сборки.

Проверьте, что:

  • образ собирается одинаково у вас и в CI — никаких «у меня работает»;
  • приложение читает настройки из переменных окружения — это критично для разных окружений;
  • контейнер слушает порт внутри окружения Kubernetes — обычно 0.0.0.0:$PORT;
  • процесс корректно завершается по сигналу SIGTERM: в Node.js это process.on('SIGTERM', ...), в Python — signal handler, иначе под будет висеть до истечения terminationGracePeriodSeconds (по умолчанию 30 секунд), а потом убьётся принудительно.

2. Опишите Deployment

Deployment отвечает за желаемое состояние: сколько копий сервиса должно работать и какой образ нужно запускать. Минимальный манифест выглядит примерно так:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-web-app
spec:
  replicas: 2
  selector:
    matchLabels:
      app: my-web-app
  template:
    metadata:
      labels:
        app: my-web-app
    spec:
      containers:
      - name: app
        image: myregistry/my-web-app:v1.2.3
        ports:
        - containerPort: 3000
        resources:
          requests:
            cpu: 100m
            memory: 128Mi
          limits:
            cpu: 200m
            memory: 256Mi
        readinessProbe:
          httpGet:
            path: /healthz
            port: 3000
          initialDelaySeconds: 5
          periodSeconds: 5
        livenessProbe:
          httpGet:
            path: /healthz
            port: 3000
          initialDelaySeconds: 15
          periodSeconds: 10

Минимально важно задать: имя приложения, количество реплик, образ, порт, ресурсы CPU и памяти, health checks и стратегию обновления (по умолчанию RollingUpdate, можно настроить maxSurge и maxUnavailable).

3. Создайте Service

Service нужен, чтобы не обращаться к конкретному Pod напрямую. Pod может быть пересоздан, его IP меняется, а Service остаётся стабильной точкой входа. Обычно для внутреннего доступа достаточно ClusterIP. Пример:

apiVersion: v1
kind: Service
metadata:
  name: my-web-app
spec:
  selector:
    app: my-web-app
  ports:
  - port: 80
    targetPort: 3000

Этот сервис будет доступен внутри кластера по имени my-web-app и порту 80, а трафик пойдёт на поды с меткой app: my-web-app на порт 3000.

4. Добавьте Ingress

Если приложение должно открываться по домену, а не по внутреннему адресу, используйте Ingress. Это особенно удобно для нескольких сервисов за одним входом. Потребуется Ingress Controller (например, nginx-ingress). Пример манифеста:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-web-app
spec:
  rules:
  - host: myapp.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: my-web-app
            port:
              number: 80

После применения и настройки DNS трафик на myapp.example.com пойдёт в ваш сервис.

5. Настройте конфигурацию

Конфигурацию лучше не вшивать в образ. Используйте:

  • ConfigMap для обычных параметров — можно монтировать как файл или инжектить через envFrom;
  • Secret для паролей, токенов и ключей — предпочтительно монтировать как том, чтобы не светить в переменных окружения.

Пример ConfigMap и его использования в Deployment:

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  DATABASE_URL: "postgres://db:5432/mydb"
  LOG_LEVEL: "info"
---
# в Deployment:
envFrom:
- configMapRef:
    name: app-config

Как выглядит рабочий поток разработчика

Веб-разработчику не нужно постоянно «сидеть в Kubernetes». Нормальный цикл выглядит проще. В реальности часто используют kubectl set image deployment/my-web-app app=myregistry/my-web-app:v1.2.4 — это триггерит rolling update. Или применяют обновлённый YAML с новым тегом образа. Ключевой момент: тег образа должен быть уникальным (например, git commit hash), а не latest, иначе rollback превратится в гадание.

  1. Изменили код.
  2. Собрали новый образ с уникальным тегом.
  3. Запушили его в registry.
  4. Обновили тег в Deployment (через kubectl set image или правкой манифеста).
  5. Применили манифест (kubectl apply -f deployment.yaml).
  6. Проверили логи, доступность и поведение приложения.
  7. При проблемах сделали rollback: kubectl rollout undo deployment/my-web-app.

Что важно проверить после деплоя

  • приложение стартует без ошибок — смотрим kubectl logs и события (kubectl get events --sort-by=.metadata.creationTimestamp);
  • все readiness checks проходят — поды в статусе Running и Ready;
  • сервис доступен через Service или Ingress — можно дёрнуть curl из временного пода;
  • переменные окружения подхватились корректно — проверяем через kubectl exec и env;
  • миграции базы не сломали запуск — если они в initContainer или в коде приложения;
  • новые Pod действительно заменили старые — старые должны терминироваться, а трафик идти только на новые (благодаря readiness gate).

На что смотреть в манифестах с самого начала

Новички часто концентрируются только на том, «запустилось ли приложение». Но в Kubernetes качество манифестов влияет на стабильность не меньше, чем код. Я не раз видел, как отсутствие resource limits приводило к тому, что один под с утечкой памяти убивал всю ноду.

Критические поля, о которых часто забывают

  • resources.requests и resources.limits — requests влияют на планирование, limits на то, когда под будет throttled по CPU или убит OOM Killer’ом;
  • readinessProbe — без неё трафик пойдёт на ещё не готовый контейнер;
  • livenessProbe — должна проверять живо ли приложение, но не быть слишком агрессивной, иначе кратковременные зависания приведут к перезапускам;
  • imagePullPolicyIfNotPresent для локальной разработки, Always для прода с уникальными тегами;
  • terminationGracePeriodSeconds — сколько времени даётся приложению на корректное завершение перед принудительным убийством;
  • securityContext — запрет запуска от root, readonly filesystem и т.д. — это базовая безопасность;
  • nodeSelector и affinity, если есть требования к размещению (например, SSD-ноды или определённая зона).

Почему probes важны

Readiness probe показывает, готово ли приложение принимать трафик. Если она не настроена, pod получает трафик сразу после старта, и клиенты видят 502/503, пока приложение инициализируется. Я обычно ставлю httpGet на /healthz с initialDelaySeconds: 5 и periodSeconds: 5.

Liveness probe показывает, живо ли приложение или его уже нужно перезапустить. Важно не делать её слишком чувствительной: если проверка требует подключения к БД, кратковременный сбой сети может вызвать лавину перезапусков. Лучше проверять, что основной цикл приложения не завис.

Без этих проверок Kubernetes может отправлять трафик в ещё не готовый сервис или долго ждать зависшее приложение.

Типовые ошибки на старте

Вот ошибки, которые чаще всего мешают при первых шагах. Почти все они всплывали в моей практике или у коллег, которые начинали с Kubernetes.

1. Приложение слушает только localhost

В контейнере и в Kubernetes приложение должно слушать на 0.0.0.0, иначе оно будет недоступно извне Pod. В dev-режиме многие веб-серверы по умолчанию слушают 127.0.0.1 — достаточно явно указать 0.0.0.0 в настройках или через переменную окружения.

2. Жёстко зашитые настройки

Если URL базы, ключи API или режим запуска записаны прямо в коде, окружения быстро начинают конфликтовать. Один раз я видел, как staging-окружение легло, потому что код обращался к продакшн-базе — адрес был вшит. Выносите всё в переменные окружения и ConfigMap/Secret.

3. Нет корректного завершения

Kubernetes часто пересоздаёт Pod. Если приложение не умеет завершаться по сигналу SIGTERM, возможны обрывы запросов и порча соединений. В Go нужно ловить сигналы и gracefully shutdown сервер, в Node.js — обрабатывать SIGTERM и закрывать соединения. Иначе pod будет убит по таймауту, а клиенты получат ошибки.

4. Отсутствуют лимиты ресурсов

Без ограничений один сервис может «съесть» весь узел или, наоборот, быть убитым при нехватке памяти. Я наблюдал, как под с утечкой памяти забирал всю RAM ноды, и OOM Killer убивал не только его, но и соседние поды. Всегда задавайте resources.requests и resources.limits.

5. Путают Pod и Deployment

Pod — это контейнерный экземпляр. Deployment — управляющая оболочка, которая следит, чтобы экземпляров было столько, сколько нужно. Напрямую поды создают редко, обычно через контроллеры. Если удалить под, управляемый Deployment, он будет пересоздан — это нормально.

6. Деплой без проверки readiness

В результате трафик идёт на контейнер, который ещё не поднялся, а пользователи видят ошибки. Даже если приложение стартует быстро, всегда добавляйте readiness probe с небольшим initialDelaySeconds. Это спасёт от 5xx при деплое.

С чего начинать мониторинг и отладку

Даже на первом этапе полезно привыкать смотреть не только на код, но и на состояние кластера. kubectl — ваш основной инструмент, и несколько команд покрывают 90% потребностей.

Основные команды kubectl

  • kubectl get pods — посмотреть, что запущено, статусы и перезапуски;
  • kubectl describe pod ... — понять причину проблем: события, состояние контейнеров;
  • kubectl logs ... — увидеть логи контейнера, с флагом --previous — логи предыдущего краша;
  • kubectl get svc — проверить сервисы и их endpoints;
  • kubectl get ingress — убедиться, что входящий трафик настроен;
  • kubectl rollout status deployment/... — проверить, завершилось ли обновление;
  • kubectl rollout undo deployment/... — откатить неудачный релиз;
  • kubectl top pods — потребление ресурсов (требует metrics-server);
  • kubectl exec -it <pod> -- sh — зайти в контейнер для диагностики.

Что искать в проблемных случаях

  • ImagePullBackOff — ошибка загрузки образа: опечатка в имени, нет прав на registry, проблемы с сетью;
  • CrashLoopBackOff — контейнер падает и перезапускается: смотреть логи предыдущего контейнера (--previous), часто проблема в конфигурации или внешних зависимостях;
  • Pending — не хватает ресурсов или есть ограничения на размещение (nodeSelector, taints);
  • Readiness probe failed — приложение ещё не готово или health endpoint возвращает ошибку;
  • OOMKilled — контейнер убит по памяти: увеличить лимиты или искать утечку.

Как встроить Kubernetes в CI/CD

Для разработчика веб-приложений Kubernetes становится особенно полезным, когда он встроен в конвейер доставки. На старте не нужно городить сложные схемы — достаточно надёжного и понятного процесса.

Базовая схема

  1. Код попадает в Git.
  2. CI собирает образ с уникальным тегом (git commit hash) и прогоняет тесты.
  3. Образ публикуется в registry.
  4. CD обновляет манифесты или Helm chart: меняет тег образа и применяет kubectl apply или helm upgrade.
  5. Kubernetes выкатывает новую версию постепенно (rolling update).
  6. После проверки можно увеличивать процент трафика или завершать релиз.

В GitHub Actions это выглядит как шаги: build, push, deploy с использованием kubectl и service account с ограниченными правами. Важно, чтобы CD имел доступ только к нужному namespace и не мог править системные объекты.

Практический совет

На старте не усложняйте pipeline. Сначала добейтесь понятного процесса:

  • один образ на один релиз — уникальный тег;
  • один способ публикации — kubectl apply с манифестами в репозитории;
  • один набор манифестов — можно использовать Kustomize для вариаций по окружениям;
  • один способ отката — kubectl rollout undo.

Когда базовый цикл станет надёжным, можно добавлять canary, blue-green и автоматические проверки. Не пытайтесь сразу внедрить сложные стратегии — устанете отлаживать.

Когда Kubernetes действительно нужен, а когда нет

Kubernetes часто воспринимают как обязательный этап зрелости. Это не так. Я видел проекты, где его внедряли ради моды, а потом страдали от операционной сложности. Если у вас один сервис и низкая нагрузка, возможно, достаточно managed-решения типа Cloud Run или даже VPS с docker-compose. Но если планируется рост, Kubernetes даст гибкость.

Kubernetes оправдан, если у вас

  • несколько сервисов, которые нужно оркестрировать;
  • ожидаемый рост нагрузки и потребность в горизонтальном масштабировании;
  • частые деплои — rolling update без простоя;
  • требования к отказоустойчивости: автоматическое восстановление после сбоев;
  • несколько окружений, которые должны быть идентичны;
  • команда уже умеет работать с контейнерами и готова осваивать оркестрацию.

Пока рано переходить, если

  • у вас один небольшой сервис — overhead на поддержку кластера неоправдан;
  • релизы редкие — нет смысла в сложном оркестраторе;
  • инфраструктура очень простая — хватит docker-compose и systemd;
  • команда не готова поддерживать кластер — Kubernetes требует администрирования: обновления, безопасность, мониторинг;
  • стоимость операционной сложности выше выгоды — иногда проще взять managed Kubernetes, но и это не панацея.

Иногда более рационально сначала наладить Docker Compose, CI/CD и процесс релизов, а уже потом переносить систему в Kubernetes. Не форсируйте переход, если текущее решение работает.

Мини-чек-лист перед первым деплоем

  • приложение запускается в контейнере без ручных действий;
  • конфигурация вынесена наружу через ConfigMap и Secret;
  • сервис слушает 0.0.0.0;
  • есть readinessProbe и livenessProbe с разумными задержками;
  • заданы лимиты ресурсов (requests и limits);
  • образ хранится в registry и доступен кластеру (при необходимости настроен imagePullSecrets);
  • есть понятный rollback: уникальные теги образов, kubectl rollout undo работает;
  • логи читаются через kubectl logs;
  • вы понимаете, как попасть в приложение через Service или Ingress;
  • образ не содержит секретов (используйте multi-stage сборки или Docker secrets);
  • приложение корректно обрабатывает SIGTERM и завершается в течение terminationGracePeriodSeconds.

Рекомендуемый порядок обучения

Если нужен практический маршрут, двигайтесь так:

  1. Docker и сборка образов — без этого никуда.
  2. YAML-манифесты Kubernetes — синтаксис, основные поля.
  3. Pod, Deployment, Service — базовая тройка.
  4. ConfigMap и Secret — управление конфигурацией.
  5. Probes и ресурсы — стабильность и производительность.
  6. Ingress и домены — внешний доступ.
  7. Helm или Kustomize — управление релизами и окружениями.
  8. Автоматизация через CI/CD — связка с пайплайнами.
  9. Мониторинг и отладка — логи, метрики, события.

Такой порядок экономит время и снижает количество тупиковых попыток «сразу сделать прод». После освоения базы можно копать в сторону StatefulSet для баз данных, PersistentVolumeClaim для хранения и операторов для сложных систем.

Вывод

Kubernetes для веб-разработчика — это не абстрактная платформа для больших компаний, а практичный способ управлять контейнерными приложениями предсказуемо и масштабируемо. Начинать лучше с простого: один сервис, один образ, один Deployment, один Service, один Ingress и понятный процесс отката. Когда базовые объекты освоены, а деплой встроен в CI/CD, Kubernetes перестаёт быть сложной магией. Он становится рабочим инструментом, который помогает выпускать веб-приложения стабильнее и быстрее — без страха уронить прод в пятницу вечером.

FAQ

Что нужно знать перед началом изучения Kubernetes?

Достаточно уверенно работать с Docker, понимать основы сетей, HTTP и переменных окружения. Остальное можно добирать по ходу практики. Если вы можете собрать образ и запустить контейнер локально — вы готовы.

С какого объекта лучше начать изучение?

С Pod, затем перейти к Deployment и Service. Это базовая тройка, на которой строится большинство сценариев. Поймите, как они связаны, и дальше будет проще.

Нужно ли изучать Kubernetes, если проект маленький?

Не всегда. Если приложение одно, команда небольшая, а релизы редкие, Kubernetes может добавить лишнюю сложность. Но для роста и будущей масштабируемости он часто полезен. Оценивайте операционные издержки трезво.

Чем отличается Deployment от Pod?

Pod запускает контейнеры — это минимальная единица. Deployment управляет количеством Pod, обновлениями и восстановлением после сбоев через ReplicaSet. Проще говоря, Deployment говорит: «Держи всегда 3 пода с таким-то образом», а Pod — это сам экземпляр.

Как быстрее всего потренироваться без продакшн-кластера?

Использовать minikube, kind или k3d. Для первых экспериментов этого достаточно. Я предпочитаю kind: одна команда — и кластер готов, можно тестировать манифесты и сразу удалять.

Почему приложение работает в Docker, но не работает в Kubernetes?

Чаще всего проблема в сети, переменных окружения, портах, health checks или в том, что контейнер ожидает локальную файловую систему и ручной запуск, а не автоматическую оркестрацию. Например, в Docker Compose сервисы видят друг друга по именам контейнеров, а в Kubernetes для этого нужен Service. Или приложение слушает localhost, а не 0.0.0.0. Проверьте логи и события — обычно причина быстро находится.