Подружиться с "кубером" крайне важно и нужно для любого ИТ-специалиста
Подружиться с "кубером" крайне важно и нужно для любого ИТ-специалиста

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

Я всегда считал, что серьезные вещи начинаются с малого, но это малое должно быть самой сутью интересующего феномена. Предлагаю разобраться, как работает полнофункциональное веб-приложение, реализующее современный стек технологий и, конечно же, развёрнутое в кластере Kubernetes. Сделав первый шаг, вы всегда сможете продолжить свой путь исследования, заниматься самообразованием и отладкой разнообразных приложений, реализующих различные подходы, в том числе и самые современные.

Создаем свою домашнюю DevOps‑«песочницу»

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

Когда мы говорим про домашний ПК, то чаще всего представляем себе обычный компьютер или ноутбук с операционной системой Windows. Например, такой:

Вот так система!
Вот так система!

«Ничего примечательного!» — скажете вы. Обычный Lenovo G510 образца 2013 года с процессором устаревшей архитектуры Haswell и 12 Гб оперативной памяти, да еще и на Windows 10. Но даже такая конфигурация позволяет запустить кластер Kubernetes (minikube) и работать с ним! Благо дело, в Windows 10 и выше появилась так называемая «Встроенная подсистема для Linux» («Windows Subsystem for Linux»), которая позволяет запустить самый настоящий Ubuntu Linux на вашем компьютере под управлением ОС Windows.

? Самый надёжный способ активировать WSL — это перейти в «Панель управления»→«Программы и компоненты»→«Включение или отключение компонентов Windows» и убедиться, что пункт «Подсистема Linux для Windows» активирована.

Проверка доступности WSL
Проверка доступности WSL

Кроме того, может потребоваться установка пакета WSL Ubuntu, который традиционно доступен вот по этой ссылке.

Для версий Windows старше Windows 10 2004 достаточно выполнить в "Командной строке" или PowerShell:


wsl --install

Эта команда автоматически включит необходимые компоненты, установит ядро WSL и дистрибутив Ubuntu. После установки рекомендуется установить для WSL версию 2 по умолчанию, выполнив команду

wsl --set-default-version 2

WSL 2 работает значительно быстрее и имеет полную совместимость с системными вызовами Linux.

Для работы с кодом нам потребуется IDE. В нашем случае я рекомендую Visual Studio Code по той причине, что в этой IDE полностью реализована поддержка WSL и есть возможность работы в терминале. Просто запустите Visual Studio Code и выберите "Terminal"->"bash", как показано на рисунке:

Работа терминала в Visual Studio Code
Работа терминала в Visual Studio Code

? Поздравляю! Вы в командной строке Linux! Располагайтесь поудобнее и почувствуйте себя настоящим сисадмином!

Примечание. Здесь и далее мы будем работать под системным пользователем "root". Да, это не самая лучшая практика с точки зрения кибербезопасности и она налагает на пользователя повышенную ответственность. Всё дело в том, что команда minikube tunnel, которую мы будем в дальнейшем использовать, требует прав суперпользователя. Но об этом мы поговорим позднее.

Итак, какие же инструменты потребуются для полноценной работы DevOps-"песочницы"? Ниже я приведу таблицу, описывающую список таких инструментов, их назначение и особенности установки.

Название

Описание

Установка

Java 17 (JDK)

Java Development Kit. Необходим для запуска backend-приложений, написанных на Java

apt install openjdk-17-jdk

Docker

Фреймворк контейнеризации приложений

apt install -y docker.io
systemctl enable docker
systemctl start docker

usermod -aG docker $USER
newgrp docker

minikube

"Урезанный" Kubernetes c одной нодой - minikube

curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64 sudo install minikube-linux-amd64 /usr/local/bin/minikube

kubectl

Утилита для управления Kubernetes из командной строки

curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl

Jenkins

Сборка проекта, настройка непрерывной интеграции (Cuntinious Integration, CI)

wget -O /usr/share/keyrings/jenkins-keyring.asc https://pkg.jenkins.io/debian-stable/jenkins.io-2023.key

echo "deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc]" https://pkg.jenkins.io/debian-stable binary/ | sudo tee /etc/apt/sources.list.d/jenkins.list > /dev/null

apt update apt install jenkins -y

systemctl start jenkins sudo systemctl enable jenkins

ufw allow 8080 ufw reload

Также дополнительно можно установить такие инструменты, как Ansible, Sonatype Nexus, Python 3 - это крайне востребованные фреймворки в DevOps-практике. Но для работы нашего приложения они пока не потребуются.

Если всё сделано верно, то Jenkins начинает запускаться автоматически сразу после старта терминала в Visual Studio Code и ��тановится доступен (в том числе в гостевой ОС Windows) по адресу http://localhost:8080/ .

Согласитесь, достаточно необычно видеть интерфейс Jenkins на домашнем компьютере. Но, надо сказать, работает он быстро и в такой конфигурации. Задаем пароль администратора Jenkins и создаем там свой первый проект!

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

Название

Команда

Java 17 (JDK)

java -version

Docker

docker images

minikube

minikube start --driver=docker --force

kubectl

kubectl get pods -n default

Давайте посмотрим, как система отреагирует на запуск каждой из этих команд:

java -version

Проверка версии Java
Проверка версии Java

docker images

Вывод установленных образов Docker во внутреннем репозитории
Вывод установленных образов Docker во внутреннем репозитории

minikube start --driver=docker --force

root@ROMAN:/mnt/c/dev/a_data# minikube start --driver=docker --force
? minikube v1.34.0 on Ubuntu 24.04 (amd64)
❗ minikube skips various validations when --force is supplied; this may lead to unexpected behavior
✨ Using the docker driver based on existing profile
? The "docker" driver should not be used with root privileges. If you wish to continue as root, use --force.
...
? Enabled addons: storage-provisioner, ingress-dns, default-storageclass, dashboard, ingress
? Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default

kubectl get pods -n default

Вывод имён подов в пространстве имён "default"
Вывод имён подов в пространстве имён "default"

Ну, вот и всё! Наша DevOps-"песочница" настроена и готова к использованию! Давайте теперь попробуем изучить и запустить наше приложение!

Техническое задание

Бэкенд

Давайте построим самое простое, но полезное приложение - записную книжку. Бэкенд данного приложения должен представлять собой SpringBoot-совместимый микросервис, обменивающийся с фронтендом сообщениями в формате Json. Бэкенд должен использовать базу данных Postgres, и для простоты мы создадим всего одну таблицу A_DATA с двумя атрибутами: первичным ключом и атрибутом для хранения данных:

CREATE TABLE IF NOT EXISTS A_DATA (

    id SERIAL PRIMARY KEY,

    a_data VARCHAR(2000) NOT NULL

);

Бэкенд должен поддерживать API по добавлению, удалению и чтению кортежей из таблицы базы данных, как представлено в таблице ниже:

API

Описание

Пример ответа

curl -X GET https://mydataapp.app/api/v1/getall

Получение всех данных таблицы A_DATA в JSON-формате

{"response":"200","data":[{"id":62,"data":"New Entry"},{"id":65,"data":"Test"},{"id":67,"data":"Kubernetes управляет миром!"}]}

curl -X DELETE https://mydataapp.app/api/v1/deleterow?id=6

Удаление строки таблицы A_DATA с id=6

{"response":"200","data":null}

curl -X POST https://mydataapp.app/api/v1/addrow?data=New+Entry

Добавление новой строки таблицы A_DATA с текстом "New Entry"

{"response":"200","data":null}

Фронтенд

Фронтенд нашего приложения - это микросервис на базе NodeJS/React, который отображает таблицу A_DATA в веб-представлении, а также имеет элементы управления: большая кнопка "+" позволяет добавить новую строку в таблицу; также данные строки могут быть отредактированы inline или удалены специальной кнопкой.

Фронтенд нашего приложения. Может быть полезным, не правда ли?
Фронтенд нашего приложения. Может быть полезным, не правда ли?

Фронтенд взаимодействует с бэкендом при помощи API, описанного выше.

Под капотом

Бэкенд

Код приложений (frontend и backend) загружен на GitHub и доступен для самостоятельного изучения. Но всё же давайте рассмотрим общие механизмы построения этих приложений. Начнем с бэкенда. Его развертывание в контейнер Docker может быть осуществлено следующим образом:

docker build --no-cache -t postgres-java-app_app:latest .

Опция --no-cache тут задана, чтобы гарантированно пересобрать новый образ docker для бэкенда. Что же представляет собой Docker-файл бэкенда? Давайте рассмотрим его подробнее.

# Build stage
FROM maven:3.8.4-openjdk-17 AS build
WORKDIR /app
COPY . .
RUN mvn clean package -DskipTests

# Run stage
FROM openjdk:17-jdk-slim
WORKDIR /app
COPY --from=build /app/target/demo-0.0.1-SNAPSHOT.jar app.jar
EXPOSE 8085
ENTRYPOINT ["java", "-jar", "app.jar"]

В данном Dockerfile используется многоступенчатая сборка (multi-stage build). На первом этапе (build) используется образ Maven с JDK 17 для сборки JAR-файла приложения. На втором этапе используется облегченный образ только с JDK 17, куда копируется собранный JAR-файл из первого этапа. Приложение запускается и становится доступным на порту 8085.

Если сборка осуществляется Maven, то должен быть и pom.xml. Давайте посмотрим, из каких зависимостей он составлен. Этот pom.xml описывает типичное современное Spring Boot приложение с:


? REST API (Starter Web)
? Работой с БД через JPA/Hibernate (Starter Data JPA)
? PostgreSQL как основная СУБД
? Миграциями БД через Flyway
? Упрощением кода через Lombok
? Тестированием через Spring Test


Приложение будет работать на Java 17 с Spring Boot 3.1.0 и использовать Maven для сборки.

Особый интерес также представляет собой файл application.properties, в котором описаны настройки базы данных Postgres, а также миграция через FlyWay:

# Server port
server.port=8085

# Database configuration
spring.datasource.url=jdbc:postgresql://postgres:5432/mydatabase
spring.datasource.username=myuser
spring.datasource.password=mypassword
spring.datasource.driver-class-name=org.postgresql.Driver

# Hibernate properties
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect

# Flyway configuration
spring.flyway.enabled=true
spring.flyway.locations=classpath:db/migration
spring.flyway.baseline-on-migrate=true
server.address=0.0.0.0

Классы SpringBoot-приложения, описывающие работу с API, доступны в пакете com.example.demo.

Фронтенд

Dockerfile фронтенда выглядит следующим образом:

# Build stage
FROM node:16 AS build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build

# Production stage
FROM nginx:alpine
COPY --from=build /app/build /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

Данный Dockerfile использует многоступенчатую сборку для создания образа фронтенд-приложения. На этапе сборки (build stage) используется образ Node.js 16, где устанавливаются зависимости через npm и выполняется сборка проекта. На финальном этапе (production stage) используется облегченный образ nginx, куда копируются статические файлы из предыдущего этапа и конфигурация nginx. Итоговый образ обслуживает приложение на порту 80.

Зависимости фронтенд-приложения описаны в файле package.json. Это типичный package.json для React-приложения, созданного с помощью Create React App (CRA).

Приложение очень простое и читается практически без комментариев. Работа с API реализована в App.js.

В мире Kubernetes

Прежде чем начать рассказывать о том, как задеплоить получившееся приложение в k8s (в нашем случае minikube), мне бы хотелось вернуться к нашему Jenkins'у. Благодаря этому оркестратору CI/CD был написан Jenkinsfile, регламентирующий процесс развертывания. Давайте рассмотрим его подробнее и изучим, какие команды выполняют стадии этого Jenkinsfile.

Команда

Назначение

git url: 'https://github.com/romo0208/mydataapp.git', branch: 'master'

Клонирование Git-репозитория

# Создаем целевую директорию, если её нет

mkdir -p /mnt/c/dev/a_data # Копируем содержимое workspace рекурсивно

cp -R ./* /mnt/c/dev/a_data/

Копирование целевых файлов в рабочую директорию

nohup minikube start --driver=docker --force > /tmp/minikube.log 2>&1 &

Запуск minikube с драйвером Docker

minikube image build -t postgres-java-app_app:latest .

Сборка образа бэкенда внутри minikube docker registry

minikube image load postgres-java-app_app:latest --overwrite=true

Загрузка образа бэкенда в minikube

minikube image build -t frontend-app_frontend:latest .

Сборка образа фронтенда внутри minikube docker registry

minikube image load frontend-app_frontend:latest --overwrite=true

Загрузка образа фронтенда в minikube

kubectl apply -f ${file.name}

Применение манифестов Kubernetes

nohup minikube dashboard > /tmp/minikube-dashboard.log 2>&1 &

Запуск minikube dashboard

Как мы можем видеть, данный Jenkinsfile просто последовательно выполняет команды в Jenkins-джобе типа "Pipeline script from SCM".

Поехали собирать и деплоить!
Поехали собирать и деплоить!

Видно, что теперь мы используем не docker build, а minikube image build, что делает доступными образы наших микросервисов непосредственно во встроенном registry нашего minikube. Особый шаг - это вызов команды kubectl apply, где последовательно применяются манифесты kubernetes. Все они разворачиваются в неймспейсе mydataapp.

Давайте попробуем разобраться в их структуре. Для бэкенда мы имеем:
- deployment. Он имеет один контейнер backend с меткой (label) backend, где указан образ микросервиса и порт 8085. Из конфигмапы backend-configmap.yaml берутся данные для настройки SpringBoot-приложения. Вместе с деплойментом указан и сервис (Service), по которому будет осуществляться сетевое взаимодействие.

Деплоймент для фронтенда выглядит проще: тут, кроме указания метки и имени образа, задан адрес бэкенда и, конечно же, сервис для сетевого взаимодействия.
Для простоты отладки readiness и liveness-пробы в манифестах деплойментов закомментированы.

Микросервис с postgresql выполнен в виде отдельного под'а с собственным деплойментом. Здесь используется стандартный образ postgres:13 и еще два дополнительных манифеста:

? postgres-pvc.yaml - PersistentVolumeClaim (PVC) — это манифест Kubernetes, который запрашивает ресурсы хранилища у кластера. Если проводить аналогию, то PVC — это "заявка на выделение диска" для нашего приложения.

? postgres-secret.yaml - secret-манифест для PostgreSQL. Заданные там значения - это base64-закодированные строки "myuser", "mypassword" и "mydatabase" соответственно.

Отдельного внимания заслуживает манифест для ingress приложения:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: mydataapp-ingress
  namespace: mydataapp
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
  tls:
  - hosts:
    - mydataapp.app
    secretName: mydataapp-tls
  rules:
  - host: mydataapp.app
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: frontend-service
            port:
              number: 80
      - path: /api
        pathType: Prefix
        backend:
          service:
            name: backend-service
            port:
              number: 8085

Здесь видно, что сервисы приложения будут использовать общий хост mydataapp.app, имея лишь разные порты: 80 для фронтенда и 8085 для бэкенда.

Minikube Dashboard

Minikube c полной уверенностью можно назвать полноценным Kubernetes лишь с одной оговоркой: у Minikube всего одна нода с именем minikube. Но даже веб-интерфейс у minikube есть и выглядит он практически идентично с теми элементами управления, что предоставляет, например, OpenShift. Вот, посмотрите, как выглядят поды нашего приложения в Minikube Dashboard:

Minikube Dashboard
Minikube Dashboard
Minikube Dashboard
Minikube Dashboard

Кстати, тут тоже есть возможность быстро редактировать yaml-манифесты прямо через веб-интерфейс:

Minikube Dashboard
Minikube Dashboard

Запускается эта красота очень просто - при помощи команды

minikube dashboard

Minikube Dashboard
Minikube Dashboard

Проброс трафика в гостевую ОС

Конечно, пощупать приложение можно и внутри WSL, используя команду curl в командной строке, но гораздо интереснее увидеть веб-интерфейс нашего приложения в браузере, который в нашей конфигурации есть лишь в гостевой ОС Windows. Первым делом нужно внести в файл %SystemRoot%\System32\drivers\etc\hosts следующую строку:

127.0.0.1 mydataapp.app

Далее в командной строке WSL Ubuntu выполняем следующее:

minikube tunnel

Мы видим, что ingress mydataapp-ingress (mydataapp.app) запускается создания туннеля, пробрасывающего трафик в гостевую ОС Windows. Таким образом, обратиться к приложению мы можем при помощи браузера (например, Chrome) при переходе на https://mydataapp.app. Кстати, именно из-за этой операции, требующей повышенных привилегий, все операции проводились под системным пользователем.

Но вот беда: браузеры на основе Chromium больше не поддерживают подключение по http, а работать по https с нашим приложением они тоже отказываются из-за самоподписанного сертификата. Как же быть? На самом деле, выход есть. Нам поможет утилита mkcert, помогающая выпустить сертификат доверия.

Выпускаем сертификат и устанавливаем его по следующей инструкции:

sudo apt install libnss3-tools
curl -JLO "https://dl.filippo.io/mkcert/latest?for=linux/amd64"
chmod +x mkcert-v*-linux-amd64
sudo cp mkcert-v*-linux-amd64 /usr/local/bin/mkcert


# Установить CA в систему
mkcert -install

# Создать сертификат для домена
mkcert mydataapp.app

# Создать Secret в Kubernetes
kubectl create secret tls mydataapp-tls --cert=mydataapp.app.pem --key=mydataapp.app-key.pem -n mydataapp

Созданный tls secret прописывается в манифесте Ingress приложения:

  tls:
  - hosts:
    - mydataapp.app
    secretName: mydataapp-tls

Кроме того, полученный корневой сертификат необходимо скопировать в Windows и добавить в список доверенных. Делается это при помощи оснастки "Сертификаты" "Консоли управления (MMC)".

После всех манипуляций наше приложение наконец-то стало доступным в ОС WIndows! Давайте посмотрим на него подробнее!

Нажмем F12 в Chrome, чтобы увидеть "Инструменты разработчика" и обновим страницу. Видно, что фронтенд обратился к бэкенду, вызвав метод API "getall":

Работа приложения
Работа приложения

Давайте теперь добавим новую запись:

Добавление новой записи
Добавление новой записи

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

? Поздравляю! Вот мы и написали и отладили простейшее микросервисное приложение, развернутое в среде kubernetes с использованием Jenkins и многих современных лучших практик! Надеюсь, что эта небольшая статья поможет вам сделать первые шаги в понимании архитектуры современных веб-приложений и погрузиться в увлекательный мир kubernetes и сопутствующих ему DevOps-практик!

Дополнение

Часто задаваемые вопросы

Как посмотреть логи того или иного пода?

Рассмотрим, например, бэкенд. Получим все его поды:

kubectl get pods -n mydataapp -l app=backend

root@ROMAN:/mnt/c/dev/a_data# kubectl get pods -n mydataapp -l app=backend
NAME                       READY   STATUS    RESTARTS        AGE
backend-5799b7b9c5-vz9v4   1/1     Running   20 (149m ago)   46d
root@ROMAN:/mnt/c/dev/a_data# 

Далее выполняем:
kubectl logs -n mydataapp <имя-пода-бэкенда>, т.е. в нашем случае:


kubectl logs -n mydataapp backend-5799b7b9c5-vz9v4

А вот и наш любимый SpringBoot!
А вот и наш любимый SpringBoot!

Как «прогуляться» по файловой системе контейнера того или иного пода?

Давайте посмотрим, какие файлы лежат внутри docker-образа фронтенда. Что там нам NPM насобирал? Поступаем таким образом. Получаем поды фронтенда:

kubectl get pods -n mydataapp -l app=frontend

root@ROMAN:/mnt/c/dev/a_data# kubectl get pods -n mydataapp -l app=frontend
NAME                        READY   STATUS    RESTARTS       AGE
frontend-75fc9fbdbf-99tkg   1/1     Running   3 (156m ago)   2d4h
root@ROMAN:/mnt/c/dev/a_data# 

kubectl exec -it -n mydataapp <имя-пода-фронтенда> -- /bin/sh, т.е. в нашем случае:

kubectl exec -it -n mydataapp frontend-75fc9fbdbf-99tkg -- /bin/sh

Исследуем внутренности фронтенда
Исследуем внутренности фронтенда

Как остановить кластер minikube?

minikube stop

Как запустить тестовый под для диагностики?

kubectl run -it --rm curl-test3 --image=curlimages/curl -n mydataapp -- sh

Как перезагрузить под, например, фронтенд?

kubectl rollout restart deployment/frontend -n mydataapp

? На этом я прощаюсь с вами! Всем удачи!

Комментарии (2)


  1. ruslan_astratov
    09.11.2025 15:35

    Спасибо! Было очень интересно и полезно!


  1. WortexLinux
    09.11.2025 15:35

    Я бы советовал кубспрей/вагрант. И получается многонодовая система, аналог небольшого продакшена. А ставить это можно и на убунту, которую ставим на флэшку и не трогаем винду. Сам периодически использую такой стенд на ноуте 8cpu/16gb, вполне хватает на 5 небольших нод. Нагрузочное конечно на таком не погоняешь, но как быстрый стенд вполне стандартного k8s очень удобно. Образа можно один раз скачать и разворачиваться с них.