Разработка на Java для Kubernetes

Все о разработке высоконагруженных сервисов на Java в распределенных средах на основе Kubernetes и Istio

Как развернуть микросервис в Kubernetes — Deployment, Service, Configmap

 

Развертывание приложений в Kubernetes рассмотрим на примере двух взаимодействующих микросервисов, Первый, который называется service-back — внутренний и он будет доступен только внутри кластера. Второй назовем service-front и он будет вызываться снаружи по https. Внешний и внутренний сервисы предостаставляют REST API для поиска данных о сотрудниках компании по первым буквам ФИО, либо табельному номеру.

Запрос на поиск одного или нескольких сотрудников по первым буквам фамилии поступает в в service-front через Ingress, далее маршрутизируется на один из подов приложения service-front. Для каждого элемента массива в запросе для service-front делается отдельный запрос в service-back. Отметим, что такая архитектура взаимодействия сделана просто в качестве тестового примера и не является оптимальной

Основные конфигурационные файлы, необходимые для развертывания приложения в Kubernetes, мы рассмотрим на примере внутреннего сервиса service-back, а конфигурации для service-front будут отличаться только настройкой доступа к нему из-за пределов кластера Kubernetes и будут рассмотрены в других статьях. Тем не менее, проверить вызов service-back мы сможем, прокинув его порт на localhost и опишем этот момент в конце материала.

Генерация образа с помощью jib

После написания программного кода приложения его необходимо упаковать в docker образ, разместить этот образ в репозитории и указать ссылку на него внутри Deployment файла приложения.

Исходный образ, на основе которого далее будет создан образ приложения service-back, можно сделать самостоятельно или взять один из готовых образов из репозитория dockerhub, а потом уже залить в dockerhub готовый образ приложения.

Шаг 1. Регистрируемся на сайте https://hub.docker.com/ и создаем свой репозиторий. Добавленные пользователем образы будут доступны через url <имя_пользователя>/<имя_репозитория>:<имя_образа>:<тэг(версия)>. Один образ залитый в репозиторий можем иметь несколько версий (тэгов). Если версия не указана или в качестве версии указано latest, то это будет означать ссылку на последнюю версию образа. Но, в примере мы используем другой подход, когда версия указывается в имени образа, таким образом url образа получается следующим:

lokrusta/lokrusta-repo/service-back-0.01-SNAPSHOT

Где lokrusta – имя пользователя dockerhub, lokrusta-repo – имя репозитория пользователя, service-back – имя приложения, 0.01-SNAPSHOT – версия приложения

Возникает вопрос, а где же в ссылке на образ URL хоста репозитория и как авторизоваться в этом репозитории, если он приватный? Дело в том, что по-умолчанию, когда URL хоста не указан, kubernetes считает, что образ лежит на dockerhub и в качестве хоста берет http://docker.io/. О том как указать данные для авторизации в репозитории, если он приватный, расскажем ниже <не забыть!>. Пока же мы получили представление о том где хранятся образы и как указывается путь к ним.

Шаг 2. Выбираем базовый образ для нашего java приложения. На dockerhub хранится большое количество готовых образов, в том числе образы, содержащие jre и jdk. Нам лучше выбрать тот, что по максимуму содержит java утилиты, которые мы потом сможем использовать для отладки и диагностики приложения внутри контейнера. Для этих целей подойдет openjdk:17-jdk-oracle (ссылка).

Нужно отметить, что предпочтительнее было бы создать самим исходный образ, включив в него все необходимые нам утилиты. В частности, нам пригодились бы разные варианты команды top, curl и.т.д. Но для простоты мы пока в качестве базового возьмем готовый.

Шаг 3. Добавляем в скрипт сборки новую задачу prepareDockerImageBuild для генерации образа нашего приложения с помощью jib. С помощью jib-плагина мы можем собрать образ нашего java приложения на основе уже существующего. Ниже приведем скрипт задачи сборки для gradle:

				
					task prepareDockerImageBuild {
    doFirst {
        jib {
            from {
                image = 'openjdk:17-jdk-oracle'
                auth {
                    username = findProperty('DOCKER_USER')
                    password = findProperty('DOCKER_PASSWORD')
                }
            }
            container {
                jvmFlags = ['-Dfile.encoding=UTF-8', '-Dspring.config.location=file:/var/app/config/application.yml']
                format = 'Docker'
                mainClass = project.ext.applicationMainClass
                appRoot = '/opt/app'
            }
            to {
                image = "lokrusta/lokrusta-repo:${project.name}-${applicationVersion}"
                auth {
                    username = findProperty('DOCKER_USER')
                    password = findProperty('DOCKER_PASSWORD')
                }
            }
        }
    }
}
				
			

Чтобы скрипт работал нужно в settings.gradle указать версию плагина:

				
					pluginManagement {
    plugins {
        id 'org.springframework.boot' version "${springBootVersion}"
        id 'com.google.cloud.tools.jib' version "${jibVersion}"
        id 'org.openapi.generator' version "${openApiVersion}"
    }
}
				
			

а в build.gradle использовать сам плагин:

				
					plugins {
    id 'org.openapi.generator'
    id 'org.springframework.boot'
    id "com.google.cloud.tools.jib"
}
				
			

После добавления автоматически в цикл сборки будет включена задача jib, но нам надо еще указать зависимость задачи jib от задач prepareDockerImageBuild и springBootJar:

				
					tasks.jib.dependsOn(prepareDockerFrontImageBuild, bootJar)
				
			

Если подробно рассмотреть код задачи prepareDockerImageBuild, то мы увидим в нем 3 раздела:

Исходный образ и данные для авторизации на dockerhub (интересен факт, что для скачивания образа из public-репозитория kubernetes-ом авторизация не нужна, но для работы jib-плагина авторизоваться на dockerhub все же требуется, так как нам нужно не только скачать образ, но и залить новый образ в репозиторий)

Данные о параметрах создаваемого контейнера — тип (docker), папка в контейнере где будет лежать готовое приложение (opt/app), имя основного класса приложения и дополнительные параметры запуска. Ниже мы увидим, что команду запуска для нашего приложения можно по-умолчанию не указывать <не забыть!>, но тогда нам обязательно надо указать необходимые параметры запуска в jvmFlags при сборке образа. В данном случае, в jvmFlags мы указываем откуда брать файл настроек application.yml и кодировку файлов по-умолчанию

Третий раздел задает куда необходимо положить создаваемый образ

Создаем Deployment

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

Deployment

Основной вариант описания пользовательских микросервисов в Kubernetes

StatefulSet

Отличается от Deployment тем, что каждый экземпляр приложения имеет уникальный идентификатор и экземпляры не взаимозаменяемы. Используется при размещении в кластере баз данных, систем сбора метрик итд

Daemon

Так размещают служебные микросервисы, когда требуется один экземпляр на ноду. Подходит, например, для развертывания служб сбора логов, таких как fluent-bit

Job

Служит для разового запуска приложения, то есть приложение запускается, выполняет свою работу и завершается. Подходит для генераторов нагрузки, например, k6

ReplicaSet

Абстракция более низкого уровня, чем Deployment. Описывает набор реплик микросервиса и неявно создается при объявлении Deployment. Напрямую применять не рекомендуется

Pod

Самый простой способ быстро и разово развернуть сервис в Kubernetes, но не подходит для управления размещением, так как после удаления пода вся информация о развертывании сервиса теряется. Неявно создается более высокими абстракциями. Напрямую применять не рекомендуется

Ниже приведен рабочий вариант манифеста Deployment для микросервиса service-back:

				
					
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: service-back
    version: "0.01"
    group: service-back
  name: service-back-0.01
  namespace: myproject
spec:
  replicas: 1
  selector:
    matchLabels:
      app: service-back
      version: "0.01"
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 25%
      maxSurge: 100%
  template:
    metadata:
      labels:
        app: service-back
        version: "0.01"
    spec:
      containers:
        - name: service-back
          image: "lokrusta/lokrusta-repo:service-back-0.01-SNAPSHOT"
          imagePullPolicy: Always
          workingDir: /opt/app
          command: [ 'java','-Dspring.config.location=file:/var/app/config/application.yml','-Dfile.encoding=UTF-8', '-cp', '@/opt/app/jib-classpath-file', '@/opt/app/jib-main-class-file', '-Xms256m', '-Xmx512m' ]
          ports:
            - containerPort: 8080
              protocol: TCP
            - containerPort: 8081
              protocol: TCP
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8081
            initialDelaySeconds: 10
            timeoutSeconds: 5
            periodSeconds: 10
            successThreshold: 1
            failureThreshold: 6
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: 8081
            initialDelaySeconds: 10
            timeoutSeconds: 5
            periodSeconds: 5
            successThreshold: 1
            failureThreshold: 6
          resources:
            limits:
              cpu: 1000m
              memory: 1Gi
            requests:
              cpu: 1000m
              memory: 1Gi
          volumeMounts:
            - name: dump-volume
              mountPath: /dump
            - name: config
              mountPath: /var/app/config/application.yml
              subPath: application.yml
            - name: log
              mountPath: /opt/app/log
        - name: logging-app
          image: busybox:1.28
          workingDir: /opt/app/log
          args: [ /bin/sh, -c, 'tail -n+1 -F application.log' ]
          imagePullPolicy: Always
          volumeMounts:
            - name: log
              mountPath: /opt/app/log
      volumes:
        - name: dump-volume
          hostPath:
            path: /var/tmp
            type: Directory
        - name: config
          configMap:
            name: service-back-cm-0.01
        - name: log
          emptyDir: {}
				
			

Рассмотрим все поля в Deployment подробно

 

Первый строчкой указано API, которое используется при указании атрибутов Deployment. Разные варианты API поддерживают разные наборы типов манифестов Kubernetes. А тип манифеста и API можно сравнить со схемой в XML-документе — они определяют набор, тип и значения других полей. Поддержка дополнительных типов манифестов (Custom Resource Definitions) реализуется через специальное API для описания расширений и устанавливается отдельно, в то время как базовые типы поставляются в составе Kubernetes изначально.

Далее указан тип конфигурации в манифесте. Это Deployment

Метки, пространство и управление масштабированием

 

В разделе metadata указаны метки нашего приложения, его имя и пространство где оно должно размещаться. Метки и имя приложения важны для ссылки на эти атрибуты в других конфигурациях, чтобы можно было указать к каким ресурсам эти конфигурации применяются. Пространство или нэймспейс можно указать в самом Deployment или задавать его параметром в kubectl:

-n <имя_пространства>

чтоб указать к какому пространству данная команда применяется

В блоке spec указано 3 раздела — replicas, selector.matchLabels и strategy. Через replicas указано количество подов, которое необходимо создать для данного Deployment. Пока мы указали самый простой вариант с одним подом, но для гибкого масштабирования можно указывать атрибуты maxReplicas и minReplicas, а количество реплик может меняться динамически через HorizontalPodAutoScaler в зависимости от утилизации ресурсов на подах.

В блоке selector.matchLabels повторно указываются метки, но эти метки относятся к подам, в то время как метки в блоке metadata относились к самому Deployment. Ниже в блоке template.metadata.labels метки указываются в третий раз. Это может путать, но главное знать, что метки в selector.matchLabels и template.metadata.labels должны совпадать и по ним Kubernetes будет искать нужные поды, а метки в разделе metadata нужны, чтоб по ним фильтровать поиск нужных манифестов в web консоли Kubernetes.

 

Управление обновлениями

 

Далее в блоке strategy описывается способ обновления подов при выпуске новый версий сервиса и перенакате Deployment-а. Перенакат Deployment может быть связан не только с изменением конфигурации внутри Deployment, а и с изменением самого образа. Тип обновления RollingUpdate указывает на то, что необходимо постепенно обновлять поды и, если очередной обновленный под не пройдет проверки живучести, то процесс обновления надо откатить. В настройках maxUnavailable и maxSurge указано на сколько можно уменьшить и увеличить количество доступных подов сервиса при обновлении. При указанных настройках обновление будет происходить следующим образом:

— будет создан еще один под service-back

новый под должен пройти проверки liveness и readiness

на новый под перенаправляется весь трафик

— старый под выводится из под нагрузки, но еще некоторое время работает после того, как на него перестает перенаправляться трафик

Такая схема обновления безопасна с точки зрения минимизации потерь клиентских запросов и обеспечения доступности, однако и при таком подходе может, что-то пойти не так. Например, под уже выведенный из под нагрузки очень долго выполняет клиентский запрос и таймаута, после которого старый под останавливается, не хватило. Также надо понимать, что при обновлении параллельно будут работать 2 версии приложения. Версия n и версия n+1. Соответственно, новая версия должна обязательно быть обратно совместима со старой. Но и это еще не все. Что если обновление произошло успешно, но из-за ошибки, допущенной в программном коде обновленное приложение не работает как надо. А ведь уже весь трафик переключен на новую версию и 100% пользователей начинают получать ошибки. Чтоб решить эту проблему используются канареечные обновления и A/B тестирование и об этом будет рассказано в других статьях. А пока идем дальше

 

Образ приложения и команда запуска

 

Пожалуй, самый главный элемент в конфигурации Deployment, это массив template.spec.containers, где описываются какие контейнеры должны содержаться в поде и их свойства. Мы рассмотрим в данной статье только главный контейнер — service-back. Дополнительный контейнер для логирования и его функции будут описаны в других статьях. У каждого контейнера есть имя и ссылка на образ. Если образ содержится в приватном репозитории, то для доступа к нему используются ссылки на секреты, указываемые в разделе imagePullSecrets, но в нашем примере это не нужно. Важным атрибутом является стратегия imagePullPolicy, которая задает в каких случаях нужно перезакачивать образ из репозитория. Значение Allways говорит о том, что надо перезаливать образ всегда и если репозиторий будет недоступен, то поды не обновятся.

При создании образа контейнера мы указали рабочую папку приложения, ее же указываем в поле workingDir.

Далее следует необязательный атрибут command, где задается команда запуска нашего приложения, но мы могли бы обойтись и без нее, достаточно только было бы указать настройки java в переменной окружения JAVA_ENV_OPTIONS. Но есть нюанс, что JAVA_ENV_OPTIONS применялись бы к запуску любой java утилиты на поде (например, к jstack или jmap), а параметры указанные в команде запуска (-Xms и проч) применяются только к запуску нашего основного приложения.

Стоит также обратить внимание, что в команде запуска имя исполняемого файла и путь к библиотекам указывают на имена файлов, в которых указана соответствующая информация, перед которыми стоит символ @:

				
					command: [ 'java','-Dspring.config.location=file:/var/app/config/application.yml','-Dfile.encoding=UTF-8', '-cp', '@/opt/app/jib-classpath-file', '@/opt/app/jib-main-class-file', '-Xms256m', '-Xmx512m' ]
				
			

Подробнее пока на этом останавливаться не будем, но если зайти в консоль пода и в папке opt/app/ дать команду:

				
					cat jib-classpath-file
				
			

то мы в этом убедимся

Порты основного контейнера

Порты контейнеров в конфигурации Deployment указываются только для информации, чтобы разработчики и администраторы могли ориентироваться в протокола и номерах портов, используемых приложением.

Проверки живучести

В разделах livenessProbe и readinessProbe указываются настройки URL и таймаутов по которым Kubernetes периодически отсылает запросы, чтобы проверить, что приложение работоспособно и готово принимать запросы. Если не проходит readinessProbe, то трафик на под перестает поступать, если не проходит livenessProbe, то Kubernetes пересоздает контейнер. В нашем примере указаны стандартные URL Spring Boot приложения, которые обеспечивает Spring Boot Actuator. Более подробно о проверках живучести, прогревах и о том как снизить вероятность потерь трафика при запросах на неработающие поды мы расскажем в отдельной статье. А в данном примере мы указали, что начинаем проверки живучести через 10 секунд после старта контейнера, повторяем их каждые 5 секунд. В случае хоть одного успешного запроса считаем, что проверка прошла. Если было 6 неуспешных запросов , то считаем, что проверка не прошла.

Ресурсы контейнера

В ресурсах указывается количество оперативной памяти и процессора, которые под запрашивает для контейнера. При этом в requests указываются минимальные требования, а в limits максимальное ограничение. Если limits не задан или совпадает с requests, то запрашиваемое изначально количество ресурсов не может увеличиваться и Kubernetes предпочтет убить под с ошибкой OOMKilled, чем назначит ему дополнительные ресурсы. В нашем примере запрошено 1 ядро процессора и 1G оперативки, при этом приложению мы выделили максимум 512 мегабайт (не считая того, что может быть доступно через использование прямых буферов. Подробнее об этом в статье Все ли мы знаем про OOM Killed).

Монтирование томов и папок

Последним, но очень важным элементом в нашей Deployment конфигурации будет монтирование томов и папок в контейнеры. Микросервис service-back использует файловую систему контейнера для следущих целей:

— Размещение конфигурационного файла application.yaml

Размещение логов приложения

Нужно отметить, что application.yaml мы могли бы хранить и внутри образа, но тогда лишились бы возможности менять настройки без пересборки образа и перенаката deployment конфигурации.

Файлы логов можно было бы и не формировать, а писать их только в консоль, откуда они бы централизовано отправлялись kubernetes-ом в лог файлы на поде. Но наше приложение отдельно пишет логи еще и в файле на поде и эти файлы обрабатываются служебным контейнером loging-app. Для чего так сделано вы можете прочесть в статьях про логирование.

Также стандартным вариантом использования файловой системы пода является размещение в ней файлов ключей и сертификатов, которые необходимы для tls-взаимодействия с инфраструктурой — базой данных, брокерами сообщений итд. Но в нашем примере такого нет.

И, наконец, мы выделяем отдельную папку на хосте, куда будут помещаться файлы дампов памяти, потоков итд. Для настройки формирования и записи дампов при падении контейнера используются дополнительные элементы конфигурации. Об этом читайте тут.

Для монтирования томов используется раздел volumes. Причем, тома монтируются для всего пода, а папки для каждого контейнера по отдельности. В нашем примере монтируется 3 разных типа томов:

dump-volume – папка на ноде для помещения в нее дампов вручную и самим приложением

config – том для хранения настроечного файла application.yaml

log – том для записи логов

Для контейнера service-back монтируются следующие файлы и папки:

Папка /dump на ноде для размещения дампов на томе dump-volume

Файл /var/app/config/application.yml в файловой системе контейнера на томе config

Папка /opt/app/log в файловой системе контейнера на томе log

Управление настройками приложения в конфигурациях ConfigMap

Ресурсы ConfigMap используются для хранения пар ключ-значение, причем значение может быть файлом из набора строк. В нашем примере мы храним в ConfigMap единственный ключ application.yaml, а его значением является конфигурационный файл для Spring Boot приложения:

				
					
apiVersion: v1
kind: ConfigMap
metadata:
  name: service-back-cm-0.01
  namespace: myproject
  labels:
    version: "0.01"
    app: service-back
    group: service-back
data:
  application.yml: |-
    logging:
      level:
        lokrusta.sample: info
        org.springframework: info
        org.springframework.boot.availability: debug
      console:
        filter:
          level: info
    server:
      port: 8080
      shutdown: graceful
      http2:
        enabled: true
    management:
      server:
        port: 8081
      endpoint:
        prometheus:
          enabled: true
        health:
          probes:
            enabled: true
      endpoints:
        web:
          exposure:
            include: "health,prometheus"
      metrics:
        export:
          prometheus:
            enabled: true
    app:
      metric:
        histogramProperties:
          percentiles: 0.5, 0.8, 0.95
          minExpectedValue: 1
          maxExpectedValue: 10000
          serviceLevelObjectives: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200, 225, 250, 275, 300, 350, 400, 450, 500, 600, 700, 800, 900, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000
          timerServiceLevelObjectives: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200, 225, 250, 275, 300, 350, 400, 450, 500, 600, 700, 800, 900, 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000
      workload:
        minTimeout: 50
        maxTimeout: 150
      common:
        timeoutMs: 2000
      db:
        json: true
				
			

Далее, в разделе volumeMounts Deployment-конфигурации указано, что значение из поля application.yaml данной ConfigMap надо поместить в файл /var/app/config/application.yml, а этот путь, в свою очередь, указывается в команде запуска нашего Spring Boot приложения.

Такой подход, как уже отмечалось выше, позволяет менять настройки микросервиса, внося изменения только в ConfigMap и далее перезапуская поды service-back (удаляя их в консоли Kubernetes) или перенакатывая Deployment файл для service-back.

Нужно отметить, что многие настройки в application.yaml, а также некоторые значения в самом Deployment-файле необходимо параметризовать в зависимости от среды размещения контейнера. Так на разных средах могут быть разные настройки соединения с БД или, как в нашем примере, вообще, в качестве БД может использоваться json-файл с данными, могут быть разные настройки ресурсов приложения итд

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

Сами настройки их ConfigMap для service-back мы подробно описывать не будем, так как разработка описание разработки тестового микросервиса выходит за рамки данной статьи, но на некоторых моментах все же остановимся.

Во-первых, отметим, что в application.yaml мы размещаем как встроенные настройки Spring Boot, так и прикладные настройки нашего микросервиса. Прикладные настройки размещены в разделе app.

В настройках для Spring Boot указано:

— Параметры логирования (а сами настройки для logback размещены в logback-spring файле внутри приложения)

— Порт поднимаемого спрингбутом http сервера, включение протокола http2, а также graceful останов сервера (чтобы дать время на завершение выполняющихся запросов)

— В разделе management указан служебный порт для Spring Boot Actuator, а также настройки Spring Boot Actuator

В настройках самого приложения указаны:

— Настройки для гистограмм и перцентилей в метриках. Об этом можно подробно прочитать тут

— Настройка workload для эмуляции задержки ответа от сервиса ( в тестовых целях)

— Настройка timeoutMs, задающая максимальное время выполнения запроса, при превышении которого service-back сам прервет выполнение операции и освободит выполняющийся поток

— Настройка db, в которой указываются параметры подключения к БД, либо как в нашем примере значение json: true, если мы хотим использовать в качестве базы данных сотрудников просто json-файл внутри приложения.

 

Доступ к микросервису service-back через манифест Service

 

Наш микросервис service-back предоставляет http-доступ для запросов к Web-серверу на порту 8080 для получения данных о сотрудниках и доступ на порту 8081 для получения метрик и healthcheck-запросов. Чтобы открыть внутри Kubernetes эти точки нужно объявить конфигурацию Service:

				
					apiVersion: v1
kind: Service
metadata:
  name: service-back
  labels:
    app: service-back
    service: service-back
  namespace: myproject
spec:
  ports:
    - port: 8080
      name: http-8080
    - port: 8081
      name: http-8081
  selector:
    app: service-back
				
			

Очень важно, что в конце указан selector:

				
					  
  selector:
    app: service-back
				
			

По этой метке Kubernetes будет понимать к каким нодам применить данную конфигурацию. Чтобы этот манифест был применен к нодам нашего Deployment-файла в нем мы указывали:

				
					  
  selector:
    matchLabels:
      app: service-back
      version: "0.01"
				
			

В спецификации сервиса указано, что открыты http порты 8080 и 8081, Запросы приходящие на данный сервис по этим портам будут балансироваться между подами приложения service-back. Таким образом, объект Service обеспечивает еще и балансировку нагрузки, но в данном случае только на уровне подов (не нод!). У каждой конфигурации Service есть тип. Если он явно не указан, то считается что используется тип ClusterIP. Сервисы этого типа не доступны за пределами Kubernetes. Наш микросервис service-back является внутренним и вызывается внешним сервисом service-front, поэтому его нельзя вызвать извне. Но в тестовых целях мы можем открыть доступ к сервису с нашего локального узла командой (где service-back-0.01-6c797fccf6-p6zmz — имя пода):

				
					kubectl port-forward service-back-0.01-6c797fccf6-p6zmz 8080 8081 -n myproject
				
			

Далее вызвать сервис, например, через Postman:

Чтобы накатить в Kubernetes созданные нами конфигурации, нужно выполнить команды в следующей последовательности:

				
					kubectl apply -f C:\Work\Projects\SampleOsApp\service-back\os\service-back\kubernetes\service\app_cm.yaml -n myproject
kubectl apply -f C:\Work\Projects\SampleOsApp\service-back\os\service-back\kubernetes\service\app_deployment.yaml -n myproject
kubectl apply -f C:\Work\Projects\SampleOsApp\service-back\os\service-back\kubernetes\service\app_service.yaml -n myproject

				
			

Если мы все сделали правильно, Kubernetes создаст в пространстве myproject нужные артефакты и один под сервиса service-back

Поделиться

LEAVE A REPLYYour email address will not be published. Required fields are marked *Your Name

Copyright © 2025 Разработка на Java для Kubernetes