Docker
Что такое Docker
Docker — это платформа для контейнеризации приложений, которая позволяет упаковать приложение со всеми его зависимостями в легковесный, переносимый контейнер. Контейнер может запускаться на любой системе, поддерживающей Docker.
Основные концепции
Image (Образ) — read-only шаблон для создания контейнеров. Содержит файловую систему, runtime, библиотеки, переменные окружения и конфигурацию.
Container (Контейнер) — запущенный экземпляр образа. Изолированная среда выполнения с собственной файловой системой, сетью и процессами.
Dockerfile — текстовый файл с инструкциями для сборки образа. Описывает каждый шаг создания образа как отдельный layer.
Layer (Слой) — промежуточный образ, созданный каждой инструкцией в Dockerfile. Layers кешируются и переиспользуются.
Registry — хранилище Docker образов (Docker Hub, AWS ECR, Google Container Registry).
Преимущества контейнеризации
- Consistency — одинаковое поведение в dev, test и production
- Isolation — изоляция приложений друг от друга
- Portability — запуск на любой платформе с Docker
- Scalability — легкое масштабирование и orchestration
- Efficiency — быстрый старт, меньше ресурсов чем VMs
Пояснение: Docker решает проблему "у меня работает" — если приложение работает в контейнере локально, оно будет работать везде.
Создание Dockerfile
Базовая структура
# Базовый образ
FROM openjdk:17-jdk-slim
# Метаданные
LABEL maintainer="developer@company.com"
LABEL version="1.0"
LABEL description="Spring Boot application"
# Рабочая директория
WORKDIR /app
# Копирование файлов
COPY target/myapp.jar app.jar
# Переменные окружения
ENV SPRING_PROFILES_ACTIVE=docker
ENV SERVER_PORT=8080
# Открытие порта
EXPOSE 8080
# Команда запуска
CMD ["java", "-jar", "app.jar"]
Инструкции Dockerfile
FROM — определяет базовый образ:
# Официальный OpenJDK образ
FROM openjdk:17-jdk-slim
# Многоступенчатая сборка
FROM maven:3.8-openjdk-17 AS build
FROM openjdk:17-jre-slim AS runtime
WORKDIR — устанавливает рабочую директорию:
WORKDIR /app
# Все последующие команды выполняются в /app
COPY vs ADD:
# COPY - простое копирование (рекомендуется)
COPY src/ /app/src/
COPY target/app.jar app.jar
# ADD - расширенное копирование (автоматическая распаковка архивов)
ADD https://example.com/file.tar.gz /tmp/
ADD archive.tar.gz /extracted/ # Автоматически распакует
RUN — выполнение команд при сборке:
# Установка зависимостей
RUN apt-get update && apt-get install -y \
curl \
vim \
&& rm -rf /var/lib/apt/lists/*
# Создание пользователя
RUN groupadd -r appuser && useradd -r -g appuser appuser
# Maven сборка
RUN mvn clean package -DskipTests
ENV — переменные окружения:
ENV JAVA_OPTS="-Xmx512m -Xms256m"
ENV DATABASE_URL="jdbc:postgresql://db:5432/myapp"
ENV SPRING_PROFILES_ACTIVE="docker"
EXPOSE — документирует порты:
EXPOSE 8080 8443 # HTTP и HTTPS порты
# Не открывает порты, только документирует
USER — переключение пользователя:
# Создание non-root пользователя для безопасности
RUN addgroup --system appuser && adduser --system --group appuser
USER appuser
CMD vs ENTRYPOINT:
# CMD - команда по умолчанию (можно переопределить)
CMD ["java", "-jar", "app.jar"]
# ENTRYPOINT - точка входа (нельзя переопределить)
ENTRYPOINT ["java", "-jar", "app.jar"]
# Комбинация ENTRYPOINT + CMD
ENTRYPOINT ["java"]
CMD ["-jar", "app.jar"] # Можно переопределить параметры
Пояснение: CMD можно полностью заменить при запуске контейнера, ENTRYPOINT — нет. Комбинация позволяет фиксировать executable и делать параметры настраиваемыми.
Multi-stage Build
Зачем нужны многоступенчатые сборки
Проблемы single-stage builds:
- Большой размер образа (build tools в production образе)
- Security risks (компиляторы и исходники в production)
- Сложность maintenance (все в одном образе)
Multi-stage build позволяет:
- Использовать разные базовые образы для build и runtime
- Копировать только необходимые артефакты
- Значительно уменьшить размер финального образа
Maven/Gradle приложения
# === BUILD STAGE ===
FROM maven:3.8-openjdk-17-slim AS build
# Копируем pom.xml для dependency caching
COPY pom.xml .
RUN mvn dependency:go-offline -B
# Копируем исходники и собираем
COPY src src
RUN mvn clean package -DskipTests
# === RUNTIME STAGE ===
FROM openjdk:17-jre-slim AS runtime
# Создаем non-root пользователя
RUN addgroup --system appuser && adduser --system --group appuser
# Рабочая директория
WORKDIR /app
# Копируем только JAR из build stage
COPY --from=build target/*.jar app.jar
# Переключаемся на non-root пользователя
USER appuser
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/actuator/health || exit 1
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
Node.js приложения
# === BUILD STAGE ===
FROM node:18-alpine AS build
WORKDIR /app
# Package files для dependency caching
COPY package*.json ./
RUN npm ci --only=production
# Исходники и сборка
COPY . .
RUN npm run build
# === RUNTIME STAGE ===
FROM node:18-alpine AS runtime
# Безопасность
RUN addgroup -g 1001 -S nodejs
RUN adduser -S nextjs -u 1001
WORKDIR /app
# Копируем production dependencies
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
COPY --from=build /app/package.json ./package.json
USER nextjs
EXPOSE 3000
CMD ["npm", "start"]
Оптимизированная сборка с кешированием
# === DEPENDENCIES STAGE ===
FROM maven:3.8-openjdk-17-slim AS deps
WORKDIR /app
COPY pom.xml .
# Загружаем зависимости (кешируется если pom.xml не изменился)
RUN mvn dependency:go-offline -B
# === BUILD STAGE ===
FROM deps AS build
# Копируем исходники
COPY src src
# Сборка приложения
RUN mvn clean package -DskipTests -o # -o = offline mode
# === TEST STAGE (опционально) ===
FROM build AS test
RUN mvn test
# === RUNTIME STAGE ===
FROM gcr.io/distroless/java17-debian11 AS runtime
WORKDIR /app
# Копируем только JAR файл
COPY --from=build /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
Пояснение: Distroless образы содержат только runtime и ваше приложение, без OS utilities. Это максимально безопасно и минимально по размеру.
Оптимизация образов
Layer Caching Strategy
Принципы эффективного кеширования:
- Копируйте dependency files перед исходниками
- Располагайте наименее изменяемые инструкции вверху
- Группируйте связанные команды в одну RUN инструкцию
- Используйте .dockerignore для исключения ненужных файлов
# ❌ Плохо - пересборка всего при изменении кода
FROM maven:3.8-openjdk-17-slim
COPY . /app
WORKDIR /app
RUN mvn clean package
# ✅ Хорошо - кеширование зависимостей
FROM maven:3.8-openjdk-17-slim
WORKDIR /app
# 1. Копируем dependency файлы (изменяются редко)
COPY pom.xml .
RUN mvn dependency:go-offline -B
# 2. Копируем исходники (изменяются часто)
COPY src src
RUN mvn clean package -DskipTests -o
.dockerignore файл
# Версионирование
.git
.gitignore
.github
# IDE файлы
.idea
.vscode
*.iml
.settings
.project
.classpath
# Build артефакты
target/
build/
*.jar
*.war
# OS файлы
.DS_Store
Thumbs.db
# Logs
*.log
logs/
# Временные файлы
*.tmp
*.temp
*.cache
# Dependencies (для Node.js)
node_modules/
npm-debug.log
# Documentation
README.md
docs/
*.md
# Docker файлы
Dockerfile
docker-compose.yml
.dockerignore
# Environment файлы (безопасность)
.env
.env.local
secrets/
Пояснение: .dockerignore работает как .gitignore, но для Docker context. Уменьшает размер context и ускоряет сборку.
Выбор базовых образов
Размер образов по типам:
# Full OS images (избегайте в production)
ubuntu:20.04 # ~72MB
centos:8 # ~200MB
# Slim variants (хороший баланс)
openjdk:17-jdk-slim # ~400MB
node:18-slim # ~180MB
python:3.9-slim # ~45MB
# Alpine variants (минимальный размер)
openjdk:17-jdk-alpine # ~350MB
node:18-alpine # ~110MB
python:3.9-alpine # ~25MB
# Distroless (максимальная безопасность)
gcr.io/distroless/java17 # ~250MB
gcr.io/distroless/nodejs # ~150MB
Оптимизация слоев
# ❌ Плохо - много слоев
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y vim
RUN apt-get clean
RUN rm -rf /var/lib/apt/lists/*
# ✅ Хорошо - один слой
RUN apt-get update && \
apt-get install -y \
curl \
vim && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# ❌ Плохо - оставляет кеш в слое
RUN wget https://example.com/large-file.tar.gz && \
tar -xzf large-file.tar.gz && \
rm large-file.tar.gz # Файл все еще в слое!
# ✅ Хорошо - очистка в том же слое
RUN wget https://example.com/large-file.tar.gz && \
tar -xzf large-file.tar.gz && \
rm large-file.tar.gz
Анализ размера образа
# Просмотр слоев образа
docker history myapp:latest
# Анализ размера с помощью dive
docker run --rm -it \
-v /var/run/docker.sock:/var/run/docker.sock \
wagoodman/dive:latest myapp:latest
# Сравнение размеров
docker images | grep myapp
Docker Compose для локальной разработки
Что такое Docker Compose
Docker Compose — инструмент для определения и запуска multi-container Docker приложений. Использует YAML файл для конфигурации services, networks и volumes.
Преимущества для разработки:
- Одна команда для запуска всех зависимостей
- Изоляция окружений разработчиков
- Consistent environment для команды
- Easy integration testing
Базовая структура docker-compose.yml
version: '3.8'
services:
app:
build: .
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=docker
depends_on:
- db
- redis
networks:
- app-network
db:
image: postgres:14-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_USER: user
POSTGRES_PASSWORD: password
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- app-network
volumes:
postgres_data:
networks:
app-network:
driver: bridge
Полная инфраструктура для микросервисов
version: '3.8'
services:
# === DATABASES ===
postgres:
image: postgres:14-alpine
environment:
POSTGRES_DB: userdb
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
- ./docker/postgres/init.sql:/docker-entrypoint-initdb.d/init.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: orderdb
MYSQL_USER: user
MYSQL_PASSWORD: password
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
- ./docker/mysql/conf.d:/etc/mysql/conf.d
command: --default-authentication-plugin=mysql_native_password
# === MESSAGE BROKERS ===
rabbitmq:
image: rabbitmq:3.11-management-alpine
environment:
RABBITMQ_DEFAULT_USER: admin
RABBITMQ_DEFAULT_PASS: admin
ports:
- "5672:5672" # AMQP port
- "15672:15672" # Management UI
volumes:
- rabbitmq_data:/var/lib/rabbitmq
kafka:
image: confluentinc/cp-kafka:latest
depends_on:
- zookeeper
environment:
KAFKA_BROKER_ID: 1
KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://localhost:9092
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
ports:
- "9092:9092"
zookeeper:
image: confluentinc/cp-zookeeper:latest
environment:
ZOOKEEPER_CLIENT_PORT: 2181
ZOOKEEPER_TICK_TIME: 2000
# === CACHING ===
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
command: redis-server --appendonly yes --requirepass redis123
# === MONITORING ===
prometheus:
image: prom/prometheus:latest
ports:
- "9090:9090"
volumes:
- ./docker/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
- prometheus_data:/prometheus
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
environment:
GF_SECURITY_ADMIN_PASSWORD: admin
volumes:
- grafana_data:/var/lib/grafana
- ./docker/grafana/dashboards:/etc/grafana/provisioning/dashboards
# === SEARCH ===
elasticsearch:
image: elasticsearch:8.8.0
environment:
- discovery.type=single-node
- xpack.security.enabled=false
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ports:
- "9200:9200"
volumes:
- elasticsearch_data:/usr/share/elasticsearch/data
# === TESTING TOOLS ===
wiremock:
image: wiremock/wiremock:latest
ports:
- "8090:8080"
volumes:
- ./docker/wiremock/mappings:/home/wiremock/mappings
- ./docker/wiremock/__files:/home/wiremock/__files
volumes:
postgres_data:
mysql_data:
rabbitmq_data:
redis_data:
prometheus_data:
grafana_data:
elasticsearch_data:
networks:
default:
name: microservices-network
Profiles для разных окружений
# docker-compose.yml
version: '3.8'
services:
app:
build: .
profiles: ["app"]
depends_on:
- db
db:
image: postgres:14-alpine
# Всегда включен (без profile)
redis:
image: redis:7-alpine
profiles: ["cache"]
monitoring:
image: prometheus:latest
profiles: ["monitoring"]
# Запуск разных комбинаций
# docker-compose up # Только db
# docker-compose --profile app up # db + app
# docker-compose --profile cache up # db + redis
# docker-compose --profile monitoring up # db + monitoring
Environment-specific конфигурации
# docker-compose.override.yml (автоматически применяется)
version: '3.8'
services:
app:
environment:
- DEBUG=true
- LOG_LEVEL=debug
volumes:
- .:/app # Live reload для разработки
# docker-compose.prod.yml
version: '3.8'
services:
app:
image: myapp:latest # Pre-built образ
restart: unless-stopped
environment:
- SPRING_PROFILES_ACTIVE=production
# Запуск с specific файлом
# docker-compose -f docker-compose.yml -f docker-compose.prod.yml up
Управление секретами
Переменные окружения (ENV)
# В Dockerfile
ENV DATABASE_PASSWORD=default_password
ENV API_KEY=placeholder
# В docker-compose.yml
services:
app:
environment:
- DATABASE_PASSWORD=${DB_PASSWORD:-default}
- API_KEY=${API_KEY}
env_file:
- .env
- .env.local
# .env файл
DB_PASSWORD=secret123
API_KEY=abc123def456
SPRING_PROFILES_ACTIVE=docker
Проблемы ENV подхода:
- Переменные видны через
docker inspect
- Могут попасть в logs
- Сложно ротировать
- Нет encryption at rest
Docker Secrets (Swarm mode)
# docker-compose.yml для Docker Swarm
version: '3.8'
services:
app:
image: myapp:latest
secrets:
- db_password
- api_key
environment:
- DATABASE_PASSWORD_FILE=/run/secrets/db_password
secrets:
db_password:
file: ./secrets/db_password.txt
api_key:
external: true # Управляется через Docker CLI
# Создание secret
# echo "my_secret_password" | docker secret create db_password -
// Чтение secret в приложении
@Component
public class SecretManager {
public String getSecret(String secretName) {
try {
Path secretPath = Paths.get("/run/secrets/" + secretName);
return Files.readString(secretPath).trim();
} catch (IOException e) {
throw new RuntimeException("Failed to read secret: " + secretName, e);
}
}
}
Volumes для секретов
services:
app:
volumes:
- secrets_volume:/app/secrets:ro # Read-only mount
environment:
- SECRETS_PATH=/app/secrets
volumes:
secrets_volume:
driver: local
driver_opts:
type: tmpfs # В памяти, не на диске
device: tmpfs
o: size=100m,uid=1000
// Чтение секретов из volume
@ConfigurationProperties(prefix = "app")
public class AppConfig {
@Value("${secrets.path:/app/secrets}")
private String secretsPath;
@PostConstruct
public void loadSecrets() {
try {
Path dbPasswordFile = Paths.get(secretsPath, "db_password");
if (Files.exists(dbPasswordFile)) {
String password = Files.readString(dbPasswordFile).trim();
System.setProperty("spring.datasource.password", password);
}
} catch (IOException e) {
log.error("Failed to load secrets", e);
}
}
}
Интеграция с HashiCorp Vault
# docker-compose.yml с Vault
services:
vault:
image: vault:latest
ports:
- "8200:8200"
environment:
VAULT_DEV_ROOT_TOKEN_ID: myroot
VAULT_DEV_LISTEN_ADDRESS: 0.0.0.0:8200
cap_add:
- IPC_LOCK
app:
depends_on:
- vault
environment:
- VAULT_ADDR=http://vault:8200
- VAULT_TOKEN=${VAULT_TOKEN}
// Spring Cloud Vault интеграция
@Configuration
@VaultPropertySource("secret/myapp")
public class VaultConfig {
@Bean
public VaultTemplate vaultTemplate() {
VaultEndpoint endpoint = new VaultEndpoint();
endpoint.setHost("vault");
endpoint.setPort(8200);
endpoint.setScheme("http");
TokenAuthentication auth = new TokenAuthentication(vaultToken);
return new VaultTemplate(endpoint, auth);
}
}
Init containers для секретов
services:
secret-fetcher:
image: vault:latest
command: |
sh -c "
vault auth -method=aws
vault read -field=password secret/db > /shared/db_password
vault read -field=key secret/api > /shared/api_key
"
volumes:
- shared_secrets:/shared
environment:
- VAULT_ADDR=https://vault.company.com
app:
depends_on:
- secret-fetcher
volumes:
- shared_secrets:/app/secrets:ro
volumes:
shared_secrets:
driver: tmpfs
Пояснение: Init containers загружают секреты до запуска основного приложения. Секреты остаются в shared volume только в памяти.
Безопасность контейнеров
Non-root пользователи
# Создание dedicated пользователя
FROM openjdk:17-jre-slim
# Создаем группу и пользователя
RUN groupadd -r appuser && useradd -r -g appuser appuser
# Создаем директории с правильными permissions
RUN mkdir -p /app/data && chown -R appuser:appuser /app
WORKDIR /app
# Копируем файлы под root
COPY --chown=appuser:appuser target/app.jar app.jar
# Переключаемся на non-root пользователя
USER appuser
CMD ["java", "-jar", "app.jar"]
Security scanning
# .github/workflows/security.yml
name: Container Security Scan
on: [push, pull_request]
jobs:
security-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build Docker image
run: docker build -t myapp:${{ github.sha }} .
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: 'myapp:${{ github.sha }}'
format: 'sarif'
output: 'trivy-results.sarif'
- name: Upload Trivy scan results
uses: github/codeql-action/upload-sarif@v1
with:
sarif_file: 'trivy-results.sarif'
Runtime security
# docker-compose.yml с security constraints
services:
app:
image: myapp:latest
security_opt:
- no-new-privileges:true # Запрет privilege escalation
cap_drop:
- ALL # Убираем все capabilities
cap_add:
- NET_BIND_SERVICE # Добавляем только необходимые
read_only: true # Read-only root filesystem
tmpfs:
- /tmp # Writable tmpfs для временных файлов
- /var/cache
ulimits:
memlock: -1
nofile: 65536
Практические команды
Основные Docker команды
# === ОБРАЗЫ ===
# Сборка образа
docker build -t myapp:latest .
docker build -t myapp:v1.0 --target production . # Multi-stage
# Просмотр образов
docker images
docker image ls --filter "reference=myapp*"
# Удаление образов
docker rmi myapp:latest
docker image prune # Удаление неиспользуемых образов
# === КОНТЕЙНЕРЫ ===
# Запуск контейнера
docker run -d --name myapp -p 8080:8080 myapp:latest
docker run -it --rm myapp:latest bash # Интерактивный режим
# Просмотр контейнеров
docker ps # Запущенные
docker ps -a # Все
# Логи контейнера
docker logs myapp
docker logs -f myapp # Follow режим
# Выполнение команд в контейнере
docker exec -it myapp bash
docker exec myapp cat /app/config.properties
# Остановка и удаление
docker stop myapp
docker rm myapp
docker container prune # Удаление остановленных контейнеров
# === СИСТЕМНЫЕ КОМАНДЫ ===
# Использование дискового пространства
docker system df
# Очистка всего неиспользуемого
docker system prune -a --volumes
# Информация о Docker
docker info
docker version
Docker Compose команды
# === УПРАВЛЕНИЕ SERVICES ===
# Запуск всех services
docker-compose up
docker-compose up -d # Detached режим
# Запуск specific services
docker-compose up db redis
docker-compose up --scale app=3 # Масштабирование
# Остановка
docker-compose stop
docker-compose down # Остановка и удаление
docker-compose down -v # + удаление volumes
# === СБОРКА ===
# Пересборка образов
docker-compose build
docker-compose up --build # Сборка и запуск
# Сборка без cache
docker-compose build --no-cache
# === ЛОГИ И МОНИТОРИНГ ===
# Просмотр логов
docker-compose logs
docker-compose logs -f app # Follow конкретного service
# Статус services
docker-compose ps
docker-compose top # Процессы в контейнерах
# === ВЫПОЛНЕНИЕ КОМАНД ===
# Выполнение команды в service
docker-compose exec app bash
docker-compose exec db psql -U postgres
# Одноразовый контейнер
docker-compose run --rm app bash
Production deployment
# === CI/CD PIPELINE ===
# Сборка для production
docker build -t myapp:${BUILD_NUMBER} \
--target production \
--build-arg VERSION=${BUILD_NUMBER} .
# Тегирование
docker tag myapp:${BUILD_NUMBER} myapp:latest
docker tag myapp:${BUILD_NUMBER} registry.company.com/myapp:${BUILD_NUMBER}
# Push в registry
docker push registry.company.com/myapp:${BUILD_NUMBER}
docker push registry.company.com/myapp:latest
# === DEPLOYMENT ===
# Pull на production сервере
docker pull registry.company.com/myapp:${BUILD_NUMBER}
# Rolling update
docker service update --image registry.company.com/myapp:${BUILD_NUMBER
Kubernetes
Что такое Kubernetes
Kubernetes (K8s) — платформа оркестрации контейнеров, которая автоматизирует развертывание, масштабирование и управление приложениями. Решает ключевые проблемы:
- Service Discovery — как сервисы находят друг друга
- Load Balancing — распределение нагрузки между репликами
- Auto-scaling — автоматическое масштабирование под нагрузку
- Self-healing — автоматическое восстановление при сбоях
- Rolling Updates — обновления без простоя
Основная идея: Вы описываете желаемое состояние (3 реплики приложения), а Kubernetes поддерживает его автоматически.
Pod — базовый строительный блок
Pod — минимальная единица развертывания. Содержит один или несколько контейнеров, которые:
- Делят общую сеть (один IP)
- Делят общие volumes
- Всегда запускаются на одной машине
- Масштабируются как единое целое
apiVersion: v1
kind: Pod
metadata:
name: my-app
labels:
app: backend
spec:
containers:
- name: app
image: myapp:1.0
ports:
- containerPort: 8080
env:
- name: ENV
value: "production"
Когда использовать Pod напрямую: Почти никогда. Pods смертны — при падении Node они исчезают навсегда. Используйте Deployment.
Deployment — управление репликами
Deployment — контроллер, который создает и управляет репликами Pod'ов. Обеспечивает:
- Desired state — нужное количество реплик
- Rolling updates — плавные обновления без простоя
- Rollback — откат к предыдущей версии
- Self-healing — пересоздание упавших Pod'ов
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3 # Желаемое количество реплик
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
spec:
containers:
- name: app
image: myapp:1.0
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
Как работает Rolling Update: Создаются новые Pod'ы с новой версией, затем постепенно удаляются старые. Если что-то идет не так — автоматический откат.
Команды:
kubectl apply -f deployment.yaml
kubectl scale deployment my-app --replicas=5
kubectl set image deployment/my-app app=myapp:2.0
kubectl rollout undo deployment/my-app
Service — сетевой доступ
Service — абстракция для доступа к группе Pod'ов. Решает проблемы:
- Pod'ы имеют динамические IP, Service дает постоянный
- Load balancing между репликами
- Service discovery через DNS
Типы Service:
- ClusterIP — доступ только внутри кластера
- NodePort — доступ через порт на каждой машине
- LoadBalancer — внешний балансировщик (в облаке)
apiVersion: v1
kind: Service
metadata:
name: my-app-service
spec:
type: ClusterIP
selector:
app: backend # Какие Pod'ы включить
ports:
- port: 80 # Порт Service
targetPort: 8080 # Порт в Pod
Как это работает: Service автоматически находит все Pod'ы с label app: backend
и балансирует трафик между ними. Если Pod умирает — исключается из балансировки.
DNS: Внутри кластера сервис доступен по имени my-app-service
или полному имени my-app-service.default.svc.cluster.local
.
ConfigMap и Secret — конфигурация
ConfigMap — хранит конфигурационные данные (не секретные):
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
database.host: "postgres.example.com"
log.level: "INFO"
app.properties: |
server.port=8080
spring.profiles.active=prod
Secret — хранит секретные данные (пароли, ключи):
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
type: Opaque
stringData: # Автоматически кодируется в base64
database.password: "secret123"
api.key: "abc-def-ghi"
Использование в Pod:
spec:
containers:
- name: app
image: myapp:1.0
env:
# Переменная из ConfigMap
- name: DB_HOST
valueFrom:
configMapKeyRef:
name: app-config
key: database.host
# Переменная из Secret
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: app-secrets
key: database.password
# Все переменные сразу
envFrom:
- configMapRef:
name: app-config
Зачем разделять: ConfigMap можно посмотреть всем, Secret — только с правами. Secret автоматически кодируется и может монтироваться как файлы с ограниченными правами.
HorizontalPodAutoscaler — автомасштабирование
HPA автоматически изменяет количество реплик в зависимости от нагрузки:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: my-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-app
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70 # При 70% CPU добавлять реплики
Как работает: HPA каждые 15 секунд проверяет метрики. Если среднее потребление CPU выше 70% — добавляет реплики. Если ниже — убирает (но медленно, чтобы избежать flapping).
Требования:
- Установленный Metrics Server в кластере
- Обязательно указанные
resources.requests
в Deployment
Создание через kubectl:
kubectl autoscale deployment my-app --cpu-percent=70 --min=2 --max=10
Health Checks — проверки здоровья
Kubernetes может автоматически проверять состояние приложений:
Liveness Probe — жив ли контейнер? При неудаче — перезапускает. Readiness Probe — готов ли принимать трафик? При неудаче — исключает из Service.
spec:
containers:
- name: app
image: myapp:1.0
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 30 # Ждать 30 сек после запуска
periodSeconds: 10 # Проверять каждые 10 сек
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
Практические советы:
- Liveness проверяет внутреннее здоровье (память, deadlock)
- Readiness проверяет готовность (подключение к БД, кеш)
- Не делайте проверки слишком агрессивными — можете создать cascade failures
Другие типы проб:
# TCP проверка
livenessProbe:
tcpSocket:
port: 8080
# Выполнение команды
livenessProbe:
exec:
command:
- cat
- /tmp/healthy
Ingress — внешний доступ
Ingress — правила маршрутизации внешнего HTTP/HTTPS трафика к сервисам внутри кластера. Позволяет:
- Использовать доменные имена вместо IP
- SSL termination
- Path-based и host-based routing
Ingress Controller — компонент, который реализует правила (NGINX, Traefik, HAProxy).
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app-service
port:
number: 80
- path: /api
pathType: Prefix
backend:
service:
name: api-service
port:
number: 8080
NGINX Ingress популярные аннотации:
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/rate-limit: "100"
nginx.ingress.kubernetes.io/proxy-body-size: "10m"
cert-manager.io/cluster-issuer: "letsencrypt"
Traefik Ingress (использует CRD):
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: my-app-route
spec:
entryPoints:
- web
routes:
- match: Host(`myapp.example.com`)
kind: Rule
services:
- name: my-app-service
port: 80
Helm — пакетный менеджер
Helm — инструмент для упаковки Kubernetes приложений в Charts. Решает проблемы:
- Множество YAML файлов сложно управлять
- Разные конфигурации для dev/stage/prod
- Версioning и откаты приложений
- Зависимости между компонентами
Chart структура:
my-app/
├── Chart.yaml # Метаданные
├── values.yaml # Конфигурация по умолчанию
└── templates/ # Kubernetes манифесты с переменными
├── deployment.yaml
├── service.yaml
└── ingress.yaml
values.yaml — конфигурационные значения:
replicaCount: 1
image:
repository: myapp
tag: latest
service:
type: ClusterIP
port: 80
ingress:
enabled: false
host: myapp.local
templates/deployment.yaml — шаблон с переменными:
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Chart.Name }}
spec:
replicas: {{ .Values.replicaCount }}
template:
spec:
containers:
- name: app
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
ports:
- containerPort: 8080
Использование:
# Установка
helm install my-release ./my-app
# Обновление
helm upgrade my-release ./my-app
# Установка с кастомными значениями
helm install my-release ./my-app -f production-values.yaml
# Откат
helm rollback my-release 1
production-values.yaml для разных окружений:
replicaCount: 3
image:
tag: v1.2.0
ingress:
enabled: true
host: myapp.company.com
tls: true
Переменные окружения
Способы передачи конфигурации в Pod:
- Прямые переменные:
env:
- name: ENV
value: "production"
- Из ConfigMap:
env:
- name: DB_HOST
valueFrom:
configMapKeyRef:
name: app-config
key: database.host
- Все ключи из ConfigMap:
envFrom:
- configMapRef:
name: app-config
- Метаданные Pod через Downward API:
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
Приоритет: Прямые переменные переопределяют ConfigMap, который переопределяет Dockerfile ENV.
Логирование
Принципы логирования в Kubernetes:
- Приложения пишут в stdout/stderr
- Kubernetes собирает логи автоматически
- Централизованная обработка через DaemonSet
Просмотр логов:
kubectl logs pod-name
kubectl logs -f deployment/my-app # Follow логи
kubectl logs --previous pod-name # Логи предыдущего контейнера
Структурированные логи (JSON формат):
# ConfigMap для logback
data:
logback.xml: |
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder">
<providers>
<timestamp/>
<logLevel/>
<message/>
<mdc/>
</providers>
</encoder>
</appender>
</configuration>
Centralized logging с Fluentd:
apiVersion: apps/v1
kind: DaemonSet # Запускается на каждом Node
metadata:
name: fluentd
spec:
template:
spec:
containers:
- name: fluentd
image: fluent/fluentd-kubernetes-daemonset
volumeMounts:
- name: varlog
mountPath: /var/log
- name: containers
mountPath: /var/lib/docker/containers
volumes:
- name: varlog
hostPath:
path: /var/log
Мониторинг
Prometheus + Grafana — стандартный стек мониторинга в Kubernetes.
Автоматические метрики:
- Kubernetes предоставляет метрики Pod'ов, Node'ов, ресурсов
- Applications exposing метрики на
/metrics
endpoint
Spring Boot Actuator:
# application.yml
management:
endpoints:
web:
exposure:
include: health,info,metrics,prometheus
endpoint:
prometheus:
enabled: true
ServiceMonitor для автоматического scraping:
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: my-app-monitor
spec:
selector:
matchLabels:
app: backend
endpoints:
- port: http
path: /actuator/prometheus
Алерты в PrometheusRule:
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: my-app-alerts
spec:
groups:
- name: my-app
rules:
- alert: HighErrorRate
expr: rate(http_requests_total{status=~"5.."}[5m]) > 0.1
for: 5m
annotations:
summary: "High error rate detected"
Grafana Dashboard — визуализация метрик:
- CPU/Memory usage по Pod'ам
- Request rate и error rate
- Response time percentiles
- Business метрики
Практические команды
Основные операции:
# Применение конфигурации
kubectl apply -f deployment.yaml
kubectl apply -f ./manifests/
# Просмотр ресурсов
kubectl get pods
kubectl get services
kubectl describe pod my-app
# Логи и отладка
kubectl logs -f deployment/my-app
kubectl exec -it pod-name -- bash
# Масштабирование
kubectl scale deployment my-app --replicas=5
# Port forwarding для отладки
kubectl port-forward service/my-app 8080:80
Отладка проблем:
# Состояние кластера
kubectl get nodes
kubectl top nodes
kubectl top pods
# События
kubectl get events --sort-by=.metadata.creationTimestamp
# Проблемы с Pod'ами
kubectl describe pod problematic-pod
kubectl logs problematic-pod --previous
Best Practices
Resource Management:
- Всегда указывайте
resources.requests
иlimits
- Requests — гарантированные ресурсы
- Limits — максимальные ресурсы
- HPA работает на основе requests
Security:
- Используйте non-root пользователей
- ReadOnlyRootFilesystem где возможно
- Network Policies для изоляции
- RBAC для ограничения доступа
High Availability:
- Минимум 2 реплики для production
- PodDisruptionBudget для критичных сервисов
- Anti-affinity для распределения по Node'ам
- Health checks обязательны
Мониторинг:
- Логируйте в structured формате (JSON)
- Exposing метрики для Prometheus
- Алерты на критичные проблемы
- Dashboard'ы для observability
Заключение
Kubernetes решает ключевые проблемы современных приложений:
- Автоматическое масштабирование под нагрузку
- Self-healing при сбоях
- Zero-downtime deployments
- Service discovery и load balancing
- Consistent окружения от dev до prod
Начинайте с простого: Pod → Deployment → Service → Ingress. Добавляйте complexity по мере необходимости.
Главный принцип: Описывайте желаемое состояние, Kubernetes поддерживает его автоматически. Это переход от "как развернуть" к "что развернуть".
CI/CD пайплайны
Что такое CI/CD
Continuous Integration (CI) — практика частого слияния изменений кода в общую ветку с автоматической проверкой качества. Решает проблемы:
- Конфликты при слиянии больших изменений
- Поздняя отладка проблем
- Несовместимость между версиями
- Ручная проверка качества кода
Continuous Deployment (CD) — автоматическое развертывание проверенного кода в production. Обеспечивает:
- Быстрая доставка фич пользователям
- Уменьшение рисков через маленькие изменения
- Откат к предыдущей версии за секунды
- Consistent deployment process
Pipeline — последовательность автоматизированных шагов от commit'а до production'а. Типичный pipeline:
- Source — получение кода из Git
- Build — компиляция и сборка
- Test — unit, integration, e2e тесты
- Security — проверка уязвимостей
- Package — создание Docker образа
- Deploy — развертывание в окружения
Jenkins — классический CI/CD сервер
Jenkins — open-source автоматизационный сервер с огромной экосистемой плагинов. Основан на концепции Jobs (задач) и Pipelines.
Jenkinsfile — Pipeline as Code
pipeline {
agent any
tools {
maven 'Maven-3.8'
jdk 'OpenJDK-17'
}
environment {
DOCKER_REGISTRY = 'registry.company.com'
APP_NAME = 'myapp'
}
stages {
stage('Checkout') {
steps {
checkout scm
}
}
stage('Build') {
steps {
sh 'mvn clean compile'
}
}
stage('Test') {
parallel {
stage('Unit Tests') {
steps {
sh 'mvn test'
}
post {
always {
publishTestResults testResultsPattern: 'target/surefire-reports/*.xml'
}
}
}
stage('Lint') {
steps {
sh 'mvn checkstyle:check'
}
}
}
}
stage('Package') {
steps {
sh 'mvn package -DskipTests'
archiveArtifacts artifacts: 'target/*.jar'
}
}
stage('Docker Build') {
steps {
script {
def image = docker.build("${DOCKER_REGISTRY}/${APP_NAME}:${BUILD_NUMBER}")
docker.withRegistry("https://${DOCKER_REGISTRY}", 'docker-registry-credentials') {
image.push()
image.push('latest')
}
}
}
}
stage('Deploy to Staging') {
when {
branch 'develop'
}
steps {
sh '''
kubectl set image deployment/myapp-staging \
app=${DOCKER_REGISTRY}/${APP_NAME}:${BUILD_NUMBER} \
--namespace=staging
'''
}
}
stage('Deploy to Production') {
when {
branch 'main'
}
steps {
input message: 'Deploy to production?', ok: 'Deploy'
sh '''
kubectl set image deployment/myapp-prod \
app=${DOCKER_REGISTRY}/${APP_NAME}:${BUILD_NUMBER} \
--namespace=production
'''
}
}
}
post {
always {
cleanWs()
}
success {
slack channel: '#deployments',
message: "✅ ${env.JOB_NAME} - ${env.BUILD_NUMBER} deployed successfully"
}
failure {
slack channel: '#alerts',
message: "❌ ${env.JOB_NAME} - ${env.BUILD_NUMBER} failed"
}
}
}
Ключевые концепции Jenkins:
- Agent — где выполняется pipeline (master, slave nodes, docker)
- Tools — настроенные инструменты (Maven, JDK, Node.js)
- Environment — переменные окружения для pipeline
- Stages — логические группы шагов
- Parallel — параллельное выполнение шагов
- When — условное выполнение stages
- Post — действия после завершения (cleanup, notifications)
Parallel execution значительно ускоряет pipeline — unit tests и lint могут выполняться одновременно.
Jenkins Master-Slave архитектура
Master Node — центральный сервер, который:
- Управляет web UI и API
- Хранит конфигурацию jobs
- Планирует выполнение builds
- Распределяет нагрузку по slave nodes
Slave Nodes (Agents) — рабочие машины, которые:
- Выполняют builds
- Могут иметь специфичную конфигурацию (OS, tools)
- Подключаются к master через SSH, JNLP или Docker
pipeline {
agent {
label 'linux && docker' // Выполнить на slave с такими labels
}
// или
agent {
docker {
image 'maven:3.8-openjdk-17'
args '-v $HOME/.m2:/root/.m2' // Maven cache
}
}
}
GitLab CI — современный встроенный CI/CD
GitLab CI — встроенная в GitLab система CI/CD с YAML-конфигурацией. Преимущества:
- Интеграция с Git из коробки
- Современный Docker-first подход
- Встроенный Container Registry
- Kubernetes integration
.gitlab-ci.yml структура
# Глобальные настройки
image: maven:3.8-openjdk-17
variables:
MAVEN_OPTS: "-Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository"
DOCKER_REGISTRY: registry.gitlab.com/$CI_PROJECT_PATH
# Кеширование зависимостей
cache:
paths:
- .m2/repository/
stages:
- build
- test
- security
- package
- deploy
# Jobs
build:
stage: build
script:
- mvn clean compile
artifacts:
paths:
- target/classes/
expire_in: 1 hour
# Параллельные тесты
unit_tests:
stage: test
script:
- mvn test
artifacts:
reports:
junit: target/surefire-reports/TEST-*.xml
paths:
- target/surefire-reports/
coverage: '/Total.*?([0-9]{1,3})%/'
integration_tests:
stage: test
script:
- mvn verify -Dskip.unit.tests=true
services:
- postgres:13
- redis:6-alpine
variables:
POSTGRES_DB: testdb
POSTGRES_USER: test
POSTGRES_PASSWORD: test
lint:
stage: test
script:
- mvn checkstyle:check
- mvn spotbugs:check
artifacts:
reports:
codequality: target/checkstyle-result.xml
# Security scanning
security_scan:
stage: security
script:
- mvn dependency-check:check
artifacts:
reports:
dependency_scanning: target/dependency-check-report.json
allow_failure: true
# Docker образ
docker_build:
stage: package
image: docker:latest
services:
- docker:dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- docker build -t $DOCKER_REGISTRY:$CI_COMMIT_SHA .
- docker push $DOCKER_REGISTRY:$CI_COMMIT_SHA
- docker tag $DOCKER_REGISTRY:$CI_COMMIT_SHA $DOCKER_REGISTRY:latest
- docker push $DOCKER_REGISTRY:latest
only:
- main
- develop
# Deployment environments
deploy_staging:
stage: deploy
script:
- kubectl set image deployment/myapp app=$DOCKER_REGISTRY:$CI_COMMIT_SHA
environment:
name: staging
url: https://staging.myapp.com
only:
- develop
deploy_production:
stage: deploy
script:
- kubectl set image deployment/myapp app=$DOCKER_REGISTRY:$CI_COMMIT_SHA
environment:
name: production
url: https://myapp.com
when: manual # Ручное подтверждение
only:
- main
Ключевые концепции GitLab CI:
- Image — Docker образ для выполнения job'а
- Services — дополнительные контейнеры (БД, кеш) для job'а
- Artifacts — файлы, передаваемые между stages
- Cache — ускорение через сохранение зависимостей
- Environment — track deployments в разные окружения
- Only/Except — условия выполнения job'ов
Runners — исполнители pipeline'ов
GitLab Runners
GitLab Runner — агент, который выполняет CI/CD jobs. Типы:
Shared Runners — предоставляются GitLab.com:
- Бесплатные для open source
- Ограниченные ресурсы и время
- Подходят для простых проектов
Group/Project Runners — ваши собственные:
- Полный контроль над ресурсами
- Могут подключаться к внутренним сервисам
- Различные executors (Docker, Shell, Kubernetes)
# Установка GitLab Runner
curl -L https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh | sudo bash
sudo apt-get install gitlab-runner
# Регистрация runner'а
sudo gitlab-runner register \
--url https://gitlab.com/ \
--registration-token PROJECT_TOKEN \
--description "Docker Runner" \
--executor docker \
--docker-image alpine:latest
Docker Executor — каждый job выполняется в отдельном контейнере:
# .gitlab-ci.yml
job:
image: node:16
script:
- npm install
- npm test
Kubernetes Executor — jobs выполняются в Kubernetes pods:
# config.toml
[[runners]]
name = "kubernetes-runner"
executor = "kubernetes"
[runners.kubernetes]
image = "alpine:latest"
namespace = "gitlab-runner"
cpu_request = "100m"
memory_request = "128Mi"
Jenkins Agents
Jenkins Agent подключается к master различными способами:
SSH Agent — подключение через SSH:
pipeline {
agent {
label 'linux-ssh'
}
}
Docker Agent — temporary контейнер для job'а:
pipeline {
agent {
docker {
image 'maven:3.8-openjdk-17'
args '-v /tmp:/tmp'
}
}
}
Kubernetes Agent — dynamic pods в Kubernetes:
pipeline {
agent {
kubernetes {
yaml '''
apiVersion: v1
kind: Pod
spec:
containers:
- name: maven
image: maven:3.8-openjdk-17
command: [cat]
tty: true
'''
}
}
}
Триггеры pipeline'ов
GitLab CI Triggers
Push triggers — автоматический запуск при push:
job:
script: echo "Run on every push"
only:
- main
- develop
- /^feature\/.*$/ # Regex для feature веток
Merge Request triggers:
mr_job:
script: echo "Run on MR"
only:
- merge_requests
Scheduled triggers — по расписанию:
nightly_job:
script: ./run-nightly-tests.sh
only:
- schedules
Manual triggers:
deploy_prod:
script: ./deploy-prod.sh
when: manual
only:
- main
External API triggers:
curl -X POST \
-F token=TOKEN \
-F ref=main \
https://gitlab.com/api/v4/projects/PROJECT_ID/trigger/pipeline
Jenkins Triggers
SCM Polling — проверка изменений в репозитории:
pipeline {
triggers {
pollSCM('H/5 * * * *') // Каждые 5 минут
}
}
Webhook triggers — мгновенная реакция на push:
pipeline {
triggers {
githubPush()
}
}
Cron triggers — по расписанию:
pipeline {
triggers {
cron('0 2 * * 1-5') // 2 AM в будние дни
}
}
Upstream/Downstream — запуск после других jobs:
pipeline {
triggers {
upstream(upstreamProjects: 'upstream-job', threshold: hudson.model.Result.SUCCESS)
}
}
Артефакты и кеширование
GitLab Artifacts
Артефакты — файлы, сохраняемые после job'а и доступные в UI или следующих stages:
build:
script:
- mvn package
artifacts:
paths:
- target/*.jar # JAR файлы
- target/site/ # Документация
reports:
junit: target/surefire-reports/TEST-*.xml
coverage: target/site/jacoco/jacoco.xml
expire_in: 1 week # Автоматическое удаление
when: always # Сохранять даже при неудаче
test:
script:
- java -jar target/app.jar --test
dependencies:
- build # Скачать артефакты от build
Cache vs Artifacts:
- Cache — ускорение between pipeline runs (dependencies)
- Artifacts — передача данных between jobs within pipeline
Maven/Gradle кеширование
Maven cache:
variables:
MAVEN_OPTS: "-Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository"
cache:
key: ${CI_COMMIT_REF_SLUG} # Разный cache для разных веток
paths:
- .m2/repository/
build:
script:
- mvn dependency:go-offline # Загрузка зависимостей
- mvn compile
Gradle cache:
variables:
GRADLE_OPTS: "-Dorg.gradle.daemon=false"
GRADLE_USER_HOME: "$CI_PROJECT_DIR/.gradle"
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- .gradle/wrapper
- .gradle/caches
build:
script:
- ./gradlew --build-cache build
Multi-level caching strategy:
cache:
- key: global-cache
paths:
- .m2/repository/
policy: pull # Только чтение
- key: ${CI_COMMIT_REF_SLUG}
paths:
- target/
policy: pull-push # Чтение и запись
Jenkins Artifacts
pipeline {
stages {
stage('Build') {
steps {
sh 'mvn package'
archiveArtifacts artifacts: 'target/*.jar', fingerprint: true
publishTestResults testResultsPattern: 'target/surefire-reports/*.xml'
}
}
stage('Deploy') {
steps {
copyArtifacts projectName: 'upstream-job', buildNumber: '${BUILD_NUMBER}'
sh 'deploy.sh target/app.jar'
}
}
}
}
Типичный Java pipeline
Complete pipeline с всеми проверками
# GitLab CI для Java приложения
image: maven:3.8-openjdk-17
variables:
MAVEN_OPTS: "-Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository"
SONAR_USER_HOME: "${CI_PROJECT_DIR}/.sonar"
cache:
paths:
- .m2/repository/
- .sonar/cache/
stages:
- validate
- build
- test
- quality
- security
- package
- deploy
# Предварительная проверка
validate:
stage: validate
script:
- mvn validate
- mvn dependency:analyze
only:
- merge_requests
- main
- develop
# Компиляция
compile:
stage: build
script:
- mvn clean compile test-compile
artifacts:
paths:
- target/classes/
- target/test-classes/
expire_in: 1 hour
# Параллельное выполнение тестов и проверок
unit_tests:
stage: test
script:
- mvn test
artifacts:
reports:
junit: target/surefire-reports/TEST-*.xml
coverage: target/site/jacoco/jacoco.xml
paths:
- target/surefire-reports/
coverage: '/Total.*?([0-9]{1,3})%/'
integration_tests:
stage: test
script:
- mvn verify -Dskip.unit.tests=true
services:
- postgres:13
- redis:6-alpine
variables:
POSTGRES_DB: testdb
POSTGRES_USER: test
POSTGRES_PASSWORD: test
SPRING_PROFILES_ACTIVE: integration-test
# Статический анализ кода
checkstyle:
stage: quality
script:
- mvn checkstyle:check
artifacts:
reports:
codequality: target/checkstyle-result.xml
spotbugs:
stage: quality
script:
- mvn spotbugs:check
artifacts:
paths:
- target/spotbugsXml.xml
sonarqube:
stage: quality
script:
- mvn sonar:sonar
-Dsonar.projectKey=$CI_PROJECT_PATH_SLUG
-Dsonar.host.url=$SONAR_HOST_URL
-Dsonar.login=$SONAR_TOKEN
only:
- main
- develop
- merge_requests
# Security checks
dependency_check:
stage: security
script:
- mvn dependency-check:check
artifacts:
reports:
dependency_scanning: target/dependency-check-report.json
allow_failure: true
# Сборка финального артефакта
package:
stage: package
script:
- mvn package -DskipTests
artifacts:
paths:
- target/*.jar
expire_in: 1 day
# Docker image
docker_build:
stage: package
image: docker:latest
services:
- docker:dind
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
only:
- main
- develop
Работа с секретами
Environment Variables
GitLab CI Variables — через UI или API:
deploy:
script:
- echo "Deploying to $DEPLOY_URL"
- curl -H "Authorization: Bearer $API_TOKEN" $DEPLOY_URL
variables:
DEPLOY_URL: "https://api.example.com" # Public variable
# API_TOKEN устанавливается через GitLab UI как protected
Jenkins Credentials:
pipeline {
environment {
API_TOKEN = credentials('api-token-id')
DB_PASSWORD = credentials('db-password')
}
stages {
stage('Deploy') {
steps {
sh 'curl -H "Authorization: Bearer $API_TOKEN" ...'
}
}
}
}
File-based secrets
GitLab File Variables:
deploy:
script:
- gcloud auth activate-service-account --key-file $GOOGLE_CREDENTIALS
- kubectl apply -f deployment.yaml
# GOOGLE_CREDENTIALS - file variable с JSON ключом
Jenkins Secret Files:
pipeline {
stages {
stage('Deploy') {
steps {
withCredentials([file(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')]) {
sh 'kubectl apply -f deployment.yaml'
}
}
}
}
}
HashiCorp Vault Integration
GitLab + Vault:
variables:
VAULT_ADDR: "https://vault.company.com"
before_script:
- export VAULT_TOKEN=$(vault write -field=token auth/jwt/login role=gitlab jwt=$CI_JOB_JWT)
- export DB_PASSWORD=$(vault kv get -field=password secret/myapp/db)
deploy:
script:
- deploy.sh --db-password="$DB_PASSWORD"
Jenkins + Vault Plugin:
pipeline {
stages {
stage('Deploy') {
steps {
withVault([configuration: [vaultUrl: 'https://vault.company.com',
vaultCredentialId: 'vault-token'],
vaultSecrets: [[path: 'secret/myapp', secretValues: [
[envVar: 'DB_PASSWORD', vaultKey: 'password'],
[envVar: 'API_KEY', vaultKey: 'api_key']]]]]) {
sh 'deploy.sh'
}
}
}
}
}
External Secret Management
AWS Secrets Manager:
before_script:
- aws configure set aws_access_key_id $AWS_ACCESS_KEY_ID
- aws configure set aws_secret_access_key $AWS_SECRET_ACCESS_KEY
- export DB_PASSWORD=$(aws secretsmanager get-secret-value --secret-id prod/myapp/db --query SecretString --output text)
deploy:
script:
- docker run -e DATABASE_PASSWORD="$DB_PASSWORD" myapp:latest
Azure Key Vault:
before_script:
- az login --service-principal -u $AZURE_CLIENT_ID -p $AZURE_CLIENT_SECRET -t $AZURE_TENANT_ID
- export API_KEY=$(az keyvault secret show --name api-key --vault-name myapp-keyvault --query value -o tsv)
Best Practices
Pipeline Optimization
Parallel execution — ускорение через параллельность:
test:
stage: test
parallel:
matrix:
- SPRING_PROFILES_ACTIVE: [mysql, postgres, h2]
script:
- mvn test -Dspring.profiles.active=$SPRING_PROFILES_ACTIVE
Conditional execution — выполнение только при необходимости:
deploy_docs:
script: ./deploy-docs.sh
only:
changes:
- docs/**/*
- README.md
Fail fast — быстрое прекращение при критических ошибках:
stages:
- validate # Syntax, basic checks
- build # Compilation
- test # Expensive tests only after build success
Security Best Practices
Least privilege — минимальные необходимые права:
deploy_staging:
script: kubectl apply -f staging/
environment: staging
# Только определенные роли могут deploying в staging
Secret rotation — регулярная смена секретов:
# Использование temporary credentials
before_script:
- export TEMP_TOKEN=$(vault write -field=token auth/aws/login role=ci-cd)
- export VAULT_TOKEN=$TEMP_TOKEN
Audit logging — логирование всех deployment'ов:
after_script:
- echo "Deployed by $GITLAB_USER_LOGIN at $(date)" >> deployment.log
- curl -X POST $AUDIT_WEBHOOK -d "{'user':'$GITLAB_USER_LOGIN','action':'deploy'}"
Monitoring и Observability
Pipeline metrics — отслеживание производительности:
# Время выполнения pipeline
before_script:
- export PIPELINE_START=$(date +%s)
after_script:
- export PIPELINE_END=$(date +%s)
- export PIPELINE_DURATION=$((PIPELINE_END - PIPELINE_START))
- curl -X POST $METRICS_ENDPOINT -d "pipeline_duration=$PIPELINE_DURATION"
Deployment tracking:
deploy:
script:
- kubectl apply -f deployment.yaml
- kubectl annotate deployment myapp deployment.kubernetes.io/revision="$CI_COMMIT_SHA"
after_script:
- |
curl -X POST $MONITORING_WEBHOOK \
-H "Content-Type: application/json" \
-d '{
"service": "myapp",
"version": "'$CI_COMMIT_SHA'",
"environment": "'$CI_ENVIRONMENT_NAME'",
"deployed_by": "'$GITLAB_USER_LOGIN'",
"timestamp": "'$(date -Iseconds)'"
}'
Заключение
CI/CD решает критические проблемы современной разработки:
- Автоматизация рутинных процессов
- Раннее обнаружение проблем
- Consistent deployment process
- Быстрая доставка фич пользователям
- Уменьшение рисков через маленькие изменения
Выбор между Jenkins и GitLab CI:
- Jenkins — максимальная гибкость, огромная экосистема плагинов, сложность настройки
- GitLab CI — простота, современный подход, встроенная интеграция с Git
Ключевые принципы:
- Fail fast — выявляйте проблемы как можно раньше
- Parallel execution — ускоряйте pipeline через параллельность
- Cache everything — кешируйте зависимости и промежуточные результаты
- Security first — защищайте секреты и ограничивайте доступ
- Monitor everything — отслеживайте метрики и производительность pipeline'ов
Начинайте с простого — базовый build → test → deploy pipeline. Добавляйте сложность по мере роста команды и требований.
Деплой и автоматизация
GitOps — автоматизация через Git
GitOps — методология, где состояние инфраструктуры и приложений декларативно описано в Git репозитории, а специальные контроллеры автоматически синхронизируют реальное состояние с желаемым.
Основные принципы GitOps
- Declarative — вся конфигурация описана в YAML/JSON манифестах
- Versioned — все изменения отслеживаются через Git commits
- Immutable — не изменяем running state напрямую, только через Git
- Pulled — агенты в кластере сами забирают изменения из Git
Традиционный подход (Push):
CI Pipeline → Build → Test → kubectl apply → Kubernetes
GitOps подход (Pull):
CI Pipeline → Build → Test → Update Git Repo
↓
Git Repo ← ArgoCD/Flux watches ← Kubernetes Cluster
Структура GitOps репозиториев
App Repository — исходный код приложения:
myapp/
├── src/
├── Dockerfile
├── .gitlab-ci.yml
└── k8s/ # Базовые манифесты
├── deployment.yaml
└── service.yaml
Config Repository — конфигурация deployment'ов:
myapp-config/
├── environments/
│ ├── dev/
│ │ ├── kustomization.yaml
│ │ └── values.yaml
│ ├── staging/
│ │ ├── kustomization.yaml
│ │ └── values.yaml
│ └── production/
│ ├── kustomization.yaml
│ └── values.yaml
└── base/ # Общие ресурсы
├── deployment.yaml
├── service.yaml
└── kustomization.yaml
Workflow GitOps:
- Разработчик делает commit в App Repository
- CI pipeline собирает новый Docker образ с тегом
- CI автоматически обновляет image tag в Config Repository
- ArgoCD/Flux замечает изменения и деплоит новую версию
Автоматическое обновление образов
CI Pipeline обновляет Config Repository:
# .gitlab-ci.yml в App Repository
update_config:
stage: deploy
script:
# Клонируем config repository
- git clone https://oauth2:$CONFIG_REPO_TOKEN@gitlab.com/company/myapp-config.git
- cd myapp-config
# Обновляем image tag
- sed -i "s|image: myapp:.*|image: myapp:$CI_COMMIT_SHA|" environments/staging/values.yaml
# Коммитим изменения
- git config user.email "ci@company.com"
- git config user.name "CI Bot"
- git add .
- git commit -m "Update staging image to $CI_COMMIT_SHA"
- git push
only:
- develop
update_prod_config:
stage: deploy
script:
- git clone https://oauth2:$CONFIG_REPO_TOKEN@gitlab.com/company/myapp-config.git
- cd myapp-config
- sed -i "s|image: myapp:.*|image: myapp:$CI_COMMIT_TAG|" environments/production/values.yaml
- git add .
- git commit -m "Update production image to $CI_COMMIT_TAG"
- git push
only:
- tags
when: manual # Ручное подтверждение для production
Deployment Strategies — стратегии развертывания
Rolling Update — постепенное обновление
Как работает: Kubernetes постепенно заменяет старые Pod'ы новыми. В каждый момент времени часть старых и новых Pod'ов работают параллельно.
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 6
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1 # Максимум 1 Pod может быть недоступен
maxSurge: 2 # Максимум 2 дополнительных Pod'а
template:
spec:
containers:
- name: app
image: myapp:v2.0
readinessProbe: # Критично для Rolling Update
httpGet:
path: /health
port: 8080
Процесс Rolling Update:
- Создаются 2 новых Pod'а (maxSurge=2) → всего 8 Pod'ов
- Когда новые Pod'ы готовы, удаляется 1 старый → 7 Pod'ов
- Создается еще 1 новый → 8 Pod'ов
- Процесс повторяется до полной замены
Преимущества: Нет простоя, постепенная миграция трафика Недостатки: Смешивание версий, сложный rollback при проблемах
Blue-Green Deployment — мгновенное переключение
Как работает: Создается полная копия приложения с новой версией (Green), затем трафик мгновенно переключается с текущей версии (Blue) на новую.
# Blue environment (текущий production)
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-blue
labels:
version: blue
spec:
replicas: 3
selector:
matchLabels:
app: myapp
version: blue
template:
metadata:
labels:
app: myapp
version: blue
spec:
containers:
- name: app
image: myapp:v1.0
---
# Green environment (новая версия)
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-green
labels:
version: green
spec:
replicas: 3
selector:
matchLabels:
app: myapp
version: green
template:
metadata:
labels:
app: myapp
version: green
spec:
containers:
- name: app
image: myapp:v2.0
---
# Service переключается между blue и green
apiVersion: v1
kind: Service
metadata:
name: myapp-service
spec:
selector:
app: myapp
version: blue # Изменить на green для переключения
ports:
- port: 80
targetPort: 8080
Процесс Blue-Green:
- Blue (v1.0) обслуживает production трафик
- Деплоим Green (v2.0) параллельно
- Тестируем Green через internal endpoint
- Переключаем Service с Blue на Green
- Мониторим Green, в случае проблем — быстрый rollback на Blue
Преимущества: Мгновенное переключение, легкий rollback, нет смешивания версий Недостатки: Требует двойные ресурсы, сложно с stateful приложениями
Canary Deployment — постепенное переключение трафика
Как работает: Новая версия получает небольшую часть трафика (например, 10%), постепенно увеличивая до 100% при отсутствии проблем.
# Stable version (90% трафика)
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-stable
spec:
replicas: 9 # 90% от общего количества
selector:
matchLabels:
app: myapp
version: stable
template:
metadata:
labels:
app: myapp
version: stable
spec:
containers:
- name: app
image: myapp:v1.0
---
# Canary version (10% трафика)
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-canary
spec:
replicas: 1 # 10% от общего количества
selector:
matchLabels:
app: myapp
version: canary
template:
metadata:
labels:
app: myapp
version: canary
spec:
containers:
- name: app
image: myapp:v2.0
---
# Service балансирует между stable и canary
apiVersion: v1
kind: Service
metadata:
name: myapp-service
spec:
selector:
app: myapp # Включает и stable, и canary
ports:
- port: 80
targetPort: 8080
Advanced Canary с Istio:
# Istio VirtualService для точного контроля трафика
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: myapp-canary
spec:
http:
- match:
- headers:
canary:
exact: "true" # Специальный header для canary
route:
- destination:
host: myapp-service
subset: canary
- route:
- destination:
host: myapp-service
subset: stable
weight: 90
- destination:
host: myapp-service
subset: canary
weight: 10 # 10% трафика на canary
Процесс Canary:
- Деплоим canary версию с 5% трафика
- Мониторим метрики (error rate, latency, business metrics)
- Если все хорошо → увеличиваем до 20%, 50%, 100%
- При проблемах → удаляем canary, весь трафик на stable
Преимущества: Минимальный риск, раннее обнаружение проблем Недостатки: Сложность в настройке, необходимость хорошего мониторинга
Helm — пакетный менеджер для Kubernetes
Helm упрощает управление сложными Kubernetes приложениями через концепцию Charts — пакетов с параметризованными YAML манифестами.
Chart структура для разных окружений
myapp-chart/
├── Chart.yaml
├── values.yaml # Значения по умолчанию
├── values-dev.yaml # Development окружение
├── values-staging.yaml # Staging окружение
├── values-production.yaml # Production окружение
└── templates/
├── deployment.yaml
├── service.yaml
├── ingress.yaml
└── _helpers.tpl
values.yaml — базовая конфигурация:
replicaCount: 1
image:
repository: myapp
tag: latest
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
ingress:
enabled: false
resources:
requests:
memory: 256Mi
cpu: 250m
environment: development
values-production.yaml — production настройки:
replicaCount: 3
image:
tag: v1.2.0 # Фиксированная версия
pullPolicy: IfNotPresent
ingress:
enabled: true
host: myapp.company.com
tls:
enabled: true
resources:
requests:
memory: 512Mi
cpu: 500m
limits:
memory: 1Gi
cpu: 1000m
environment: production
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
templates/deployment.yaml — параметризованный манифест:
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "myapp.fullname" . }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "myapp.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "myapp.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
env:
- name: ENVIRONMENT
value: {{ .Values.environment }}
resources:
{{- toYaml .Values.resources | nindent 10 }}
Helm в GitOps workflow
GitOps + Helm структура:
myapp-gitops/
├── applications/
│ ├── dev/
│ │ └── myapp.yaml # ArgoCD Application
│ ├── staging/
│ │ └── myapp.yaml
│ └── production/
│ └── myapp.yaml
└── charts/
└── myapp/ # Helm chart
├── Chart.yaml
├── values.yaml
└── templates/
ArgoCD Application для Helm:
# applications/production/myapp.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp-production
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/company/myapp-gitops
path: charts/myapp
targetRevision: HEAD
helm:
valueFiles:
- values-production.yaml
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
Kustomize — альтернатива Helm
Kustomize — встроенный в kubectl инструмент для кастомизации Kubernetes манифестов без шаблонов. Использует принцип наследования и наложения изменений.
Kustomize структура
myapp-kustomize/
├── base/ # Базовые ресурсы
│ ├── deployment.yaml
│ ├── service.yaml
│ └── kustomization.yaml
└── overlays/ # Окружения
├── dev/
│ ├── kustomization.yaml
│ └── dev-patch.yaml
├── staging/
│ ├── kustomization.yaml
│ └── staging-patch.yaml
└── production/
├── kustomization.yaml
├── production-patch.yaml
└── hpa.yaml # Дополнительные ресурсы
base/kustomization.yaml — базовая конфигурация:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
commonLabels:
app: myapp
images:
- name: myapp
newTag: latest
overlays/production/kustomization.yaml — production кастомизация:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
bases:
- ../../base
resources:
- hpa.yaml # Дополнительные ресурсы для production
patchesStrategicMerge:
- production-patch.yaml
images:
- name: myapp
newTag: v1.2.0 # Production версия
replicas:
- name: myapp-deployment
count: 3 # 3 реплики для production
commonLabels:
environment: production
overlays/production/production-patch.yaml — патч для production:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp-deployment
spec:
template:
spec:
containers:
- name: myapp
resources:
requests:
memory: 512Mi
cpu: 500m
limits:
memory: 1Gi
cpu: 1000m
env:
- name: ENVIRONMENT
value: production
Применение Kustomize:
# Локальное применение
kubectl apply -k overlays/production/
# Просмотр результата без применения
kubectl kustomize overlays/production/
Kustomize vs Helm:
- Kustomize: Простой, декларативный, встроенный в kubectl, нет шаблонизации
- Helm: Мощная шаблонизация, пакетный менеджер, dependency management, сложнее
ArgoCD — GitOps оператор
ArgoCD — Kubernetes-native continuous delivery tool, который мониторит Git репозитории и автоматически синхронизирует состояние кластера с репозиторием.
Установка ArgoCD
# Установка ArgoCD
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
# Доступ к UI
kubectl port-forward svc/argocd-server -n argocd 8080:443
ArgoCD Application
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
source:
repoURL: https://github.com/company/myapp-config
path: k8s/production
targetRevision: HEAD
# Для Helm charts
helm:
valueFiles:
- values-production.yaml
parameters:
- name: image.tag
value: v1.2.0
# Для Kustomize
kustomize:
images:
- myapp:v1.2.0
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true # Удалять ресурсы, удаленные из Git
selfHeal: true # Автоматически исправлять drift
syncOptions:
- CreateNamespace=true
retry:
limit: 5
backoff:
duration: 5s
factor: 2
maxDuration: 3m
Multi-cluster deployment с ArgoCD
# ApplicationSet для deployment в несколько кластеров
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: myapp-multi-cluster
namespace: argocd
spec:
generators:
- clusters:
selector:
matchLabels:
environment: production
template:
metadata:
name: 'myapp-{{name}}'
spec:
project: default
source:
repoURL: https://github.com/company/myapp-config
path: k8s/production
targetRevision: HEAD
destination:
server: '{{server}}'
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
ArgoCD преимущества:
- Декларативный GitOps
- Web UI для визуализации
- Multi-cluster support
- RBAC интеграция
- Rollback capabilities
Flux CD — альтернативный GitOps оператор
Flux — более легковесная альтернатива ArgoCD с focus на simplicity и Kubernetes-native approach.
Flux v2 структура
# GitRepository — источник манифестов
apiVersion: source.toolkit.fluxcd.io/v1beta1
kind: GitRepository
metadata:
name: myapp-config
namespace: flux-system
spec:
interval: 1m
ref:
branch: main
url: https://github.com/company/myapp-config
---
# Kustomization — что деплоить
apiVersion: kustomize.toolkit.fluxcd.io/v1beta1
kind: Kustomization
metadata:
name: myapp-production
namespace: flux-system
spec:
interval: 10m
path: "./k8s/production"
prune: true
sourceRef:
kind: GitRepository
name: myapp-config
validation: client
healthChecks:
- apiVersion: apps/v1
kind: Deployment
name: myapp
namespace: production
Image automation с Flux
# ImageRepository — отслеживание Docker registry
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageRepository
metadata:
name: myapp
namespace: flux-system
spec:
image: registry.company.com/myapp
interval: 1m
---
# ImagePolicy — политика обновления образов
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImagePolicy
metadata:
name: myapp-policy
namespace: flux-system
spec:
imageRepositoryRef:
name: myapp
policy:
semver:
range: '>=1.0.0'
---
# ImageUpdateAutomation — автоматическое обновление
apiVersion: image.toolkit.fluxcd.io/v1beta1
kind: ImageUpdateAutomation
metadata:
name: myapp-auto-update
namespace: flux-system
spec:
interval: 30m
sourceRef:
kind: GitRepository
name: myapp-config
git:
checkout:
ref:
branch: main
commit:
author:
email: flux@company.com
name: Flux
messageTemplate: |
Update {{range .Updated.Images}}{{println .}}{{end}}
update:
path: "./k8s/production"
strategy: Setters
Flux vs ArgoCD:
- Flux: Kubernetes-native CRDs, lightweight, built-in image automation
- ArgoCD: Rich UI, ApplicationSets, more mature ecosystem
Практические сценарии
Hotfix deployment
Scenario: Критический bug в production, нужен быстрый hotfix.
GitOps процесс:
- Создание hotfix ветки от production tag
- Исправление bug и создание hotfix tag (v1.2.1)
- CI автоматически обновляет production config с новым тегом
- ArgoCD автоматически деплоит hotfix
# CI pipeline для hotfix
hotfix_deploy:
script:
- git clone $CONFIG_REPO
- cd myapp-config
- sed -i "s|tag: v.*|tag: $CI_COMMIT_TAG|" environments/production/values.yaml
- git commit -am "Hotfix: $CI_COMMIT_TAG"
- git push
only:
- /^v\d+\.\d+\.\d+$/ # Только version tags
when: manual
Feature flag driven deployment
Scenario: Новая фича должна быть выключена по умолчанию.
# ConfigMap для feature flags
apiVersion: v1
kind: ConfigMap
metadata:
name: feature-flags
data:
new-payment-flow: "false"
experimental-ui: "false"
enhanced-search: "true"
# Deployment с feature flags
spec:
template:
spec:
containers:
- name: app
env:
- name: FEATURE_NEW_PAYMENT_FLOW
valueFrom:
configMapKeyRef:
name: feature-flags
key: new-payment-flow
Включение фичи через GitOps:
- Изменение ConfigMap в Git:
new-payment-flow: "true"
- ArgoCD автоматически обновляет ConfigMap
- Приложение перечитывает конфигурацию без restart
Multi-environment promotion
Scenario: Код проходит через dev → staging → production.
# ArgoCD ApplicationSet для promotion
apiVersion: argoproj.io/v1alpha1
kind: ApplicationSet
metadata:
name: myapp-promotion
spec:
generators:
- list:
elements:
- environment: dev
branch: develop
cluster: dev-cluster
- environment: staging
branch: release
cluster: staging-cluster
- environment: production
branch: main
cluster: production-cluster
template:
metadata:
name: 'myapp-{{environment}}'
spec:
source:
repoURL: https://github.com/company/myapp-config
path: 'environments/{{environment}}'
targetRevision: '{{branch}}'
destination:
server: '{{cluster}}'
namespace: '{{environment}}'
Best Practices
Security в GitOps
Repository access control:
# ArgoCD Project с ограничениями
apiVersion: argoproj.io/v1alpha1
kind: AppProject
metadata:
name: production-apps
spec:
description: Production applications
sourceRepos:
- https://github.com/company/myapp-config
destinations:
- namespace: production
server: https://production-cluster
clusterResourceWhitelist:
- group: apps
kind: Deployment
namespaceResourceWhitelist:
- group: ''
kind: Service
- group: ''
kind: ConfigMap
Separate repositories:
- App code и infrastructure code в разных репозиториях
- Разные access rights для разных команд
- Production config требует code review
Monitoring GitOps
ArgoCD metrics:
# ServiceMonitor для ArgoCD
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: argocd-metrics
spec:
selector:
matchLabels:
app.kubernetes.io/name: argocd-metrics
endpoints:
- port: metrics
Key metrics для мониторинга:
- Sync status приложений
- Time to deployment (Git commit → running in production)
- Drift detection и self-healing events
- Failed deployments и их причины
Rollback strategies
Automatic rollback на проблемы:
# ArgoCD с health checks
spec:
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- Retry=3
revisionHistoryLimit: 10 # Хранить историю для rollback
Manual rollback через ArgoCD UI или CLI:
# ArgoCD CLI rollback
argocd app rollback myapp --revision 123
argocd app sync myapp
Заключение
Современный deployment — это GitOps + правильная стратегия развертывания:
- GitOps обеспечивает audit trail, rollback capabilities, и declarative approach
- Rolling Update — для большинства случаев, простой и надежный
- Blue-Green — когда нужен instant rollback и нет проблем с ресурсами
- Canary — для критичных изменений с постепенной проверкой
Выбор инструментов:
- Helm — для сложных приложений с множеством параметров
- Kustomize — для простых case'ов без шаблонизации
- ArgoCD — rich UI, mature ecosystem
- Flux — Kubernetes-native, automatic image updates
Ключевые принципы:
- Everything as Code — вся конфигурация в Git
- Immutable deployments — не изменяем running state напрямую
- Automated testing — проверка deployments через health checks
- Progressive delivery — постепенное раскатывание с мониторингом
- Fast rollback — возможность быстро откатиться при проблемах
Начинайте с простого: Rolling Updates + ArgoCD + Helm. Добавляйте Canary deployments по мере роста критичности системы.
Security & DevSecOps
Управление секретами
HashiCorp Vault
Vault — это централизованное хранилище секретов с динамическим управлением доступом. Позволяет генерировать временные секреты, ротировать их автоматически и контролировать доступ через политики.
Основные возможности:
- Динамическая генерация секретов (БД пароли, AWS ключи)
- Шифрование данных в покое и при передаче
- Аудит всех операций с секретами
- Интеграция с внешними системами аутентификации
Установка и базовая настройка:
# Запуск Vault в dev режиме
vault server -dev
# Авторизация
export VAULT_ADDR='http://127.0.0.1:8200'
vault auth -method=userpass username=admin
Работа с секретами:
# Создание секрета
vault kv put secret/myapp db_password="supersecret123"
# Чтение секрета
vault kv get secret/myapp
# Создание политики доступа
vault policy write myapp-policy - <<EOF
path "secret/data/myapp/*" {
capabilities = ["read"]
}
EOF
AWS Secrets Manager
AWS Secrets Manager — управляемый сервис для хранения секретов в AWS. Автоматически ротирует пароли для RDS, DocumentDB и других сервисов.
Ключевые особенности:
- Автоматическая ротация секретов
- Интеграция с AWS сервисами
- Шифрование с помощью AWS KMS
- Контроль доступа через IAM
Создание и использование секретов:
# Создание секрета
aws secretsmanager create-secret \
--name "prod/myapp/db" \
--description "Database credentials" \
--secret-string '{"username":"admin","password":"mysecret"}'
# Получение секрета
aws secretsmanager get-secret-value --secret-id "prod/myapp/db"
Использование в Python:
import boto3
import json
def get_secret(secret_name, region_name="us-east-1"):
session = boto3.session.Session()
client = session.client(
service_name='secretsmanager',
region_name=region_name
)
try:
get_secret_value_response = client.get_secret_value(
SecretId=secret_name
)
return json.loads(get_secret_value_response['SecretString'])
except Exception as e:
print(f"Error retrieving secret: {e}")
return None
# Использование
db_creds = get_secret("prod/myapp/db")
Сканирование уязвимостей
Snyk
Snyk — платформа для обнаружения и исправления уязвимостей в зависимостях, контейнерах и коде. Интегрируется с CI/CD и предоставляет рекомендации по устранению.
Основные функции:
- Сканирование зависимостей в различных языках
- Анализ Docker образов
- Статический анализ кода (SAST)
- Автоматические PR с исправлениями
Установка и использование:
# Установка CLI
npm install -g snyk
# Авторизация
snyk auth
# Сканирование проекта
snyk test
# Мониторинг проекта
snyk monitor
Интеграция в CI/CD (GitHub Actions):
- name: Run Snyk to check for vulnerabilities
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --severity-threshold=high
Trivy
Trivy — бесплатный сканер уязвимостей для контейнеров, файловых систем и Git репозиториев. Поддерживает множество форматов вывода и легко интегрируется в CI/CD.
Возможности:
- Сканирование OS пакетов и языковых зависимостей
- Обнаружение секретов в коде
- Проверка конфигураций Kubernetes
- Сканирование образов в реестрах
Установка и использование:
# Установка через curl
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh
# Сканирование Docker образа
trivy image nginx:latest
# Сканирование файловой системы
trivy fs /path/to/project
# Сканирование с выводом в JSON
trivy image --format json --output results.json nginx:latest
Интеграция в Docker build:
FROM alpine:latest
RUN apk add --no-cache trivy
COPY . /app
RUN trivy fs /app --exit-code 1 --severity HIGH,CRITICAL
OWASP Dependency-Check
OWASP Dependency-Check — инструмент для обнаружения публично известных уязвимостей в зависимостях проекта. Использует базу данных CVE для идентификации уязвимостей.
Особенности:
- Поддержка множества языков и пакетных менеджеров
- Интеграция с Maven, Gradle, Jenkins
- Различные форматы отчетов (HTML, XML, JSON)
- Возможность задания пороговых значений для прерывания сборки
Использование с Maven:
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>8.4.0</version>
<configuration>
<failBuildOnCVSS>7</failBuildOnCVSS>
<format>ALL</format>
</configuration>
</plugin>
CLI использование:
# Скачивание и запуск
wget https://github.com/jeremylong/DependencyCheck/releases/download/v8.4.0/dependency-check-8.4.0-release.zip
unzip dependency-check-8.4.0-release.zip
# Сканирование проекта
./dependency-check/bin/dependency-check.sh \
--project "MyProject" \
--scan "./src" \
--format "HTML" \
--out "./reports"
Настройка HTTPS и TLS
Основы TLS
TLS (Transport Layer Security) — криптографический протокол для обеспечения безопасной передачи данных. Заменил устаревший SSL.
Ключевые компоненты:
- Handshake — установление безопасного соединения
- Сертификат — подтверждение подлинности сервера
- Симметричное шифрование — для передачи данных
- Асимметричное шифрование — для обмена ключами
Настройка HTTPS в Nginx
server {
listen 443 ssl http2;
server_name example.com;
# Сертификаты
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/private.key;
# Протоколы и шифры
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# HSTS
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# Редирект с HTTP на HTTPS
server {
listen 80;
server_name example.com;
return 301 https://$server_name$request_uri;
}
Получение сертификатов с Let's Encrypt
# Установка Certbot
sudo apt-get install certbot python3-certbot-nginx
# Получение сертификата
sudo certbot --nginx -d example.com -d www.example.com
# Автоматическое обновление
sudo crontab -e
0 12 * * * /usr/bin/certbot renew --quiet
Secrets в Kubernetes
Создание и использование секретов
Kubernetes Secrets — объекты для хранения конфиденциальной информации (пароли, токены, ключи). Данные кодируются в base64 и могут быть зашифрованы в etcd.
Типы секретов:
- Opaque — произвольные данные
- kubernetes.io/dockerconfigjson — учетные данные Docker
- kubernetes.io/tls — TLS сертификаты
- kubernetes.io/service-account-token — токены сервисных аккаунтов
Создание секрета:
apiVersion: v1
kind: Secret
metadata:
name: mysecret
type: Opaque
data:
username: YWRtaW4= # admin в base64
password: MWYyZDFlMmU2N2Rm # 1f2d1e2e67df в base64
Создание через kubectl:
# Из литералов
kubectl create secret generic mysecret \
--from-literal=username=admin \
--from-literal=password=1f2d1e2e67df
# Из файлов
kubectl create secret generic mysecret \
--from-file=username.txt \
--from-file=password.txt
# TLS секрет
kubectl create secret tls tls-secret \
--cert=path/to/cert.pem \
--key=path/to/key.pem
Использование секретов в Pod
apiVersion: v1
kind: Pod
metadata:
name: secret-test-pod
spec:
containers:
- name: test-container
image: nginx
# Переменные окружения из секрета
env:
- name: SECRET_USERNAME
valueFrom:
secretKeyRef:
name: mysecret
key: username
- name: SECRET_PASSWORD
valueFrom:
secretKeyRef:
name: mysecret
key: password
# Монтирование как volume
volumeMounts:
- name: secret-volume
mountPath: /etc/secret-volume
readOnly: true
volumes:
- name: secret-volume
secret:
secretName: mysecret
Шифрование секретов в etcd
# /etc/kubernetes/encryption-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: key1
secret: <base64-encoded-secret>
- identity: {}
Добавление в kube-apiserver:
# В манифест kube-apiserver
- --encryption-provider-config=/etc/kubernetes/encryption-config.yaml
JWT и OAuth2
JWT (JSON Web Token)
JWT — стандарт для создания токенов доступа. Состоит из трех частей: заголовок, полезная нагрузка и подпись. Токены самодостаточны и содержат всю необходимую информацию.
Структура JWT:
header.payload.signature
Пример JWT токена:
// Header
{
"alg": "HS256",
"typ": "JWT"
}
// Payload
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022,
"exp": 1516242622
}
Создание JWT в Node.js:
const jwt = require('jsonwebtoken');
// Создание токена
const token = jwt.sign(
{
userId: 123,
username: 'john',
role: 'admin'
},
'your-secret-key',
{
expiresIn: '1h',
issuer: 'your-app',
audience: 'your-users'
}
);
// Проверка токена
try {
const decoded = jwt.verify(token, 'your-secret-key');
console.log(decoded);
} catch (err) {
console.error('Invalid token');
}
OAuth2
OAuth2 — протокол авторизации, позволяющий приложениям получать ограниченный доступ к ресурсам пользователя. Разделяет аутентификацию и авторизацию.
Основные роли:
- Resource Owner — владелец ресурса (пользователь)
- Client — приложение, запрашивающее доступ
- Authorization Server — сервер авторизации
- Resource Server — сервер ресурсов
Grant Types (типы предоставления доступа):
- Authorization Code — для веб-приложений
- Implicit — для SPA (устарел)
- Client Credentials — для межсервисного взаимодействия
- Resource Owner Password — для доверенных приложений
Keycloak
Keycloak — открытый сервер идентификации и управления доступом. Поддерживает OAuth2, OpenID Connect, SAML и другие протоколы.
Возможности:
- Единый вход (SSO)
- Социальная аутентификация
- Двухфакторная аутентификация
- Управление пользователями и ролями
- Адаптеры для различных платформ
Установка через Docker:
docker run -p 8080:8080 -e KEYCLOAK_ADMIN=admin -e KEYCLOAK_ADMIN_PASSWORD=admin \
quay.io/keycloak/keycloak:latest start-dev
Настройка клиента в Keycloak:
// Конфигурация клиента
const keycloakConfig = {
realm: 'myrealm',
'auth-server-url': 'http://localhost:8080',
'ssl-required': 'external',
resource: 'myclient',
'public-client': true,
'confidential-port': 0
};
// Инициализация Keycloak в JavaScript
const keycloak = new Keycloak(keycloakConfig);
keycloak.init({ onLoad: 'login-required' }).then(authenticated => {
if (authenticated) {
console.log('User authenticated');
// Получение токена
const token = keycloak.token;
const refreshToken = keycloak.refreshToken;
}
});
Интеграция с Spring Boot:
# application.yml
keycloak:
realm: myrealm
auth-server-url: http://localhost:8080
resource: myclient
public-client: true
spring:
security:
oauth2:
client:
registration:
keycloak:
client-id: myclient
authorization-grant-type: authorization_code
scope: openid, profile, email
provider:
keycloak:
authorization-uri: http://localhost:8080/realms/myrealm/protocol/openid-connect/auth
token-uri: http://localhost:8080/realms/myrealm/protocol/openid-connect/token
user-info-uri: http://localhost:8080/realms/myrealm/protocol/openid-connect/userinfo
jwk-set-uri: http://localhost:8080/realms/myrealm/protocol/openid-connect/certs
Лучшие практики безопасности
Общие принципы
- Принцип минимальных привилегий — предоставляйте минимально необходимые права
- Глубокая защита — используйте множественные уровни безопасности
- Fail securely — в случае сбоя система должна оставаться безопасной
- Регулярные обновления — своевременно применяйте патчи безопасности
Безопасность в CI/CD
# Пример безопасного pipeline
name: Secure CI/CD
on: [push, pull_request]
jobs:
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
# Сканирование секретов
- name: Scan for secrets
run: |
git secrets --scan
# Сканирование зависимостей
- name: Dependency scan
run: |
npm audit --audit-level=moderate
# Сканирование образа
- name: Container scan
run: |
docker build -t myapp .
trivy image myapp --exit-code 1 --severity HIGH,CRITICAL
# Статический анализ
- name: Static analysis
uses: github/super-linter@v4
env:
DEFAULT_BRANCH: main
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
Мониторинг и логирование
# Пример конфигурации для мониторинга безопасности
apiVersion: v1
kind: ConfigMap
metadata:
name: security-config
data:
falco.yaml: |
rules_file:
- /etc/falco/falco_rules.yaml
json_output: true
log_stderr: true
log_syslog: true
# Правила для обнаружения аномалий
- rule: Detect shell in container
desc: Detect shell execution in container
condition: >
spawned_process and container and
(proc.name in (shell_binaries))
output: >
Shell spawned in container
(user=%user.name container=%container.name
shell=%proc.name parent=%proc.pname cmdline=%proc.cmdline)
priority: WARNING
Эта шпаргалка охватывает основные аспекты безопасности в DevOps, от управления секретами до настройки аутентификации и авторизации. Каждый инструмент имеет свои особенности и области применения, поэтому важно выбирать подходящие решения в зависимости от конкретных требований проекта.
Kubernetes Health Probes в Spring Boot
Основные концепции
Health Probes — механизм мониторинга состояния подов в Kubernetes. Позволяют кластеру принимать решения о перезапуске, направлении трафика и готовности приложения.
Spring Boot Actuator — модуль для мониторинга и управления приложением, предоставляет готовые эндпоинты для health checks.
Типы пробов
1. Liveness Probe
Назначение: Определяет, жив ли контейнер. При провале — перезапускает под.
Когда использовать: Deadlock, зависание приложения, критические ошибки.
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
2. Readiness Probe
Назначение: Готов ли под принимать трафик. При провале — исключает из Service.
Когда использовать: Подключение к БД, прогрев кэша, инициализация зависимостей.
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 2
3. Startup Probe
Назначение: Проверяет завершение запуска. Блокирует другие пробы до успеха.
Когда использовать: Медленный старт, загрузка данных, legacy приложения.
startupProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 30 # 150 сек макс
Spring Boot Actuator настройка
Зависимость
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
application.yml
management:
endpoints:
web:
exposure:
include: health,info,metrics
endpoint:
health:
show-details: always
probes:
enabled: true # Включает /liveness и /readiness
health:
livenessstate:
enabled: true
readinessstate:
enabled: true
Кастомные Health Indicators
Проверка базы данных
@Component
public class DatabaseHealthIndicator implements HealthIndicator {
@Autowired
private DataSource dataSource;
@Override
public Health health() {
try (Connection conn = dataSource.getConnection()) {
if (conn.isValid(2)) {
return Health.up()
.withDetail("database", "Available")
.build();
}
} catch (Exception e) {
return Health.down()
.withDetail("error", e.getMessage())
.build();
}
return Health.down().build();
}
}
Проверка внешнего сервиса
@Component
public class ExternalServiceHealthIndicator implements HealthIndicator {
@Autowired
private RestTemplate restTemplate;
@Override
public Health health() {
try {
ResponseEntity<String> response = restTemplate
.getForEntity("http://external-service/health", String.class);
if (response.getStatusCode().is2xxSuccessful()) {
return Health.up().build();
}
} catch (Exception e) {
return Health.down()
.withException(e)
.build();
}
return Health.down().build();
}
}
Параметры конфигурации
initialDelaySeconds
Задержка перед первой проверкой. Дает время на инициализацию.
periodSeconds
Интервал между проверками. Баланс между отзывчивостью и нагрузкой.
timeoutSeconds
Таймаут ожидания ответа. Должен быть меньше periodSeconds.
failureThreshold
Количество неудачных попыток перед действием (перезапуск/исключение).
successThreshold
Количество успешных проверок для восстановления (только для readiness).
Deployment пример
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-app
spec:
replicas: 3
selector:
matchLabels:
app: spring-app
template:
metadata:
labels:
app: spring-app
spec:
containers:
- name: app
image: myapp:latest
ports:
- containerPort: 8080
env:
- name: SPRING_PROFILES_ACTIVE
value: "k8s"
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 60
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 2
startupProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
failureThreshold: 30
resources:
requests:
memory: "512Mi"
cpu: "250m"
limits:
memory: "1Gi"
cpu: "500m"
Лучшие практики
Разделение ответственности
- Liveness: Только критические ошибки (deadlock, OOM)
- Readiness: Зависимости (БД, кэш, внешние сервисы)
- Startup: Длительная инициализация
Производительность
- Легковесные проверки (< 1 сек)
- Избегать сложной логики в health checks
- Кэширование результатов для медленных зависимостей
Безопасность
management:
endpoints:
web:
base-path: /internal
exposure:
include: health
endpoint:
health:
show-details: when-authorized
Мониторинг
- Логирование изменений состояния
- Метрики проваленных проверок
- Алерты на частые перезапуски
Типичные ошибки
- Слишком агрессивные таймауты — ложные срабатывания
- Проверка внешних зависимостей в liveness — каскадные перезапуски
- Одинаковые эндпоинты для всех пробов — неправильная диагностика
- Игнорирование startup probe — преждевременные перезапуски медленных приложений
Отладка
Просмотр статуса подов
kubectl get pods
kubectl describe pod <pod-name>
kubectl logs <pod-name>
Проверка health endpoints
kubectl port-forward <pod-name> 8080:8080
curl http://localhost:8080/actuator/health