С какими проблемами сталкивался при Delivery в CI/CD
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Проблемы при Delivery в CI/CD
В процессе настройки и работы с CI/CD pipeline мне приходилось сталкиваться с различными проблемами на этапе delivery (доставки артефактов в production или staging).
1. Нестабильность развёртывания
Проблема: Один и тот же коммит развёртывается по-разному в разных окружениях.
# Плохо - переменные окружения разные
stages:
delivery:
script:
- docker run -e DB_HOST=$DB_HOST app:latest
# DB_HOST может быть разным в dev, staging, prod
Решение: Использование версионированных артефактов и конфигов
# Хорошо - явное указание версий
stages:
build:
script:
- docker build -t app:${CI_COMMIT_SHA} .
- docker push registry.example.com/app:${CI_COMMIT_SHA}
delivery:
script:
- kubectl set image deployment/app app=registry.example.com/app:${CI_COMMIT_SHA}
# Чёткие версии для каждого окружения
- kubectl apply -f config/prod-secrets.yaml
2. Database migration проблемы
Проблема: Миграции БД не откатываются при ошибке deploy
// Проблемное развёртывание
public class DeploymentConfig {
public static void main(String[] args) {
// Миграции БД выполняются
Flyway.configure()
.dataSource(dbUrl, dbUser, dbPassword)
.load()
.migrate(); // Если здесь ошибка, отката нет!
// Потом запускаем приложение
SpringApplication.run(Application.class, args);
}
}
Решение: Blue-Green deployment с возможностью отката
delivery:
before_script:
# Резервная копия текущего состояния
- kubectl get deployment app -o yaml > /tmp/app-backup.yaml
- ./scripts/backup-database.sh
script:
# Развёртываем новую версию (Green)
- kubectl apply -f deployment-new.yaml
- sleep 30 # Даём время на старт
- kubectl run test-pod --image=curlimages/curl -- curl http://app-new:8080/health
on_failure:
# При ошибке откатываемся (возвращаемся к Blue)
- kubectl apply -f /tmp/app-backup.yaml
- ./scripts/restore-database.sh
3. Артефакты не удаляются, дисковое пространство кончается
Проблема: Pipeline создаёт Docker образы и артефакты, но не удаляет старые
# Проблема: диск переполнится
build:
script:
- docker build -t app:${CI_COMMIT_SHA} .
- docker push registry.example.com/app:${CI_COMMIT_SHA}
# Но образ остаётся на диске
Решение: Очистка старых артефактов
build:
script:
- docker build -t app:${CI_COMMIT_SHA} .
- docker push registry.example.com/app:${CI_COMMIT_SHA}
- docker image prune -f --filter "until=72h" # Удалить образы старше 3 дней
- docker system prune -f --volumes # Очистить unused volumes
artifacts:
paths:
- target/app.jar
expire_in: 7 days # Автоматически удаляется через 7 дней
4.롤льбэк при критической ошибке
Проблема: Развертывание началось, но новое приложение крашится, а откатиться невозможно
// Новая версия с багом
@RestController
public class ApiController {
@PostMapping("/users")
public void createUser(@RequestBody User user) {
userService.save(user); // NullPointerException при user == null!
// Приложение крашится, но версия уже в prod
}
}
Решение: Health check перед финализацией deployment
#!/bin/bash
delivery_script() {
# 1. Развёртываем новую версию
kubectl apply -f deployment.yaml
# 2. Ждём старта подов
kubectl wait --for=condition=available --timeout=300s \
deployment/app
# 3. Проверяем здоровье приложения
for i in {1..10}; do
if curl -f http://localhost:8080/actuator/health; then
echo "Health check passed"
break
fi
if [ $i -eq 10 ]; then
echo "Health check failed, rolling back"
kubectl rollout undo deployment/app
exit 1
fi
sleep 5
done
# 4. Запускаем smoke тесты
./run-smoke-tests.sh || {
kubectl rollout undo deployment/app
exit 1
}
echo "Deployment successful"
}
5. Зависимости от порядка развёртывания микросервисов
Проблема: Сервис A зависит от сервиса B, но B развёртывается после A
// Service A пытается подключиться к Service B
@Service
public class ServiceA {
@Autowired
private RestTemplate restTemplate;
public String callServiceB() {
// Service B может быть недоступен!
return restTemplate.getForObject("http://service-b:8080/data", String.class);
}
}
Решение: Явная последовательность в CI/CD
stages:
- build
- test
- deploy-critical-services # Сначала базовые сервисы
- deploy-dependent-services # Потом зависимые
- smoke-tests
deploy-base-services:
stage: deploy-critical-services
script:
- kubectl apply -f services/database.yaml
- kubectl wait --for=condition=ready pod -l app=database --timeout=600s
- kubectl apply -f services/service-b.yaml
- kubectl wait --for=condition=ready pod -l app=service-b --timeout=600s
deploy-dependent-services:
stage: deploy-dependent-services
dependencies:
- deploy-base-services
script:
- kubectl apply -f services/service-a.yaml
6. Недостаточно прав в production
Проблема: Pipeline пытается развернуть, но не хватает прав доступа
# Проблема: используется одна учётная запись для всех окружений
deploy:
script:
- kubectl apply -f deployment.yaml
# Может не быть доступа к prod кластеру
Решение: Разные credentials для разных окружений
deploy-staging:
stage: deploy
environment:
name: staging
before_script:
- kubectl config use-context staging-cluster
- kubectl config set-credentials deployer --token=$STAGING_TOKEN
script:
- kubectl apply -f deployment.yaml
only:
- develop
deploy-prod:
stage: deploy
environment:
name: production
before_script:
- kubectl config use-context prod-cluster
- kubectl config set-credentials deployer --token=$PROD_TOKEN
script:
- kubectl apply -f deployment.yaml
only:
- main
when: manual # Требует ручного подтверждения
7. Конфликты портов при локальном тестировании
Проблема: Pipeline запускает Docker контейнеры, но порты уже занят
# Ошибка: Docker: bind: address already in use
docker run -p 8080:8080 app:latest
Решение: Использование динамических портов
# Получаем свободный порт
get_free_port() {
python3 -c "import socket; s = socket.socket(); s.bind(('', 0)); print(s.getsockname()[1])"
}
PORT=$(get_free_port)
docker run -p $PORT:8080 app:latest
# Или использование docker-compose с версионированием
docker-compose -p "app-${CI_PIPELINE_ID}" up
8. Timeout при развёртывании
Проблема: Kubernetes pod слишком долго стартует, pipeline отменяется
# По умолчанию timeout может быть недостаточным
script:
- kubectl apply -f deployment.yaml
- kubectl wait --for=condition=ready pod -l app=app --timeout=60s
# Может не успеть за 60 секунд
Решение: Правильное конфигурирование проб и timeout
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
template:
spec:
containers:
- name: app
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 30 # Даём время на старт
periodSeconds: 10
failureThreshold: 3
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 60
periodSeconds: 10
9. Утечки памяти в Java приложениях
Проблема: Старые версии приложения остаются в памяти после deployment
// Неправильный shutdown hook
public class Application {
static ExecutorService executor = Executors.newFixedThreadPool(10);
// executor не закрывается при shutdown!
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("Shutdown");
// executor.shutdown(); // Забыли это!
}));
}
Решение: Правильная конфигурация graceful shutdown
@Configuration
public class ShutdownConfig {
@Bean
public ExecutorServiceExitHandler executorServiceExitHandler(
@Qualifier("taskExecutor") ExecutorService executor) {
return new ExecutorServiceExitHandler(executor);
}
}
@Component
public class ExecutorServiceExitHandler implements DisposableBean {
private final ExecutorService executor;
public ExecutorServiceExitHandler(ExecutorService executor) {
this.executor = executor;
}
@Override
public void destroy() throws Exception {
executor.shutdown();
if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
}
}
10. Версионирование и совместимость
Проблема: API версия не совпадает между сервисами при deployment
// Service A ждёт version: 2, но Service B уже в version: 3
@RestController
public class ServiceAClient {
@GetMapping("/data")
public Data getData() {
// API изменился, поле удалено
return restTemplate.getForObject("http://service-b/api/v2/data", Data.class);
}
}
Решение: Версионирование с обратной совместимостью
// API версия явно указана
@RestController
@RequestMapping("/api/v2")
public class ServiceBController {
@GetMapping("/data")
public Map<String, Object> getData() {
Map<String, Object> response = new HashMap<>();
response.put("version", "2");
response.put("data", "value");
response.put("legacyField", "для совместимости");
return response;
}
}
Чеклист для надёжного Delivery
✅ Версионирование артефактов (Docker images с SHA коммита) ✅ Health checks перед финализацией ✅ Возможность отката (Blue-Green deployment) ✅ Правильная последовательность развёртывания сервисов ✅ Timeout с запасом ✅ Graceful shutdown приложений ✅ Очистка старых артефактов ✅ Мониторинг и алертинг ✅ Smoke tests после развёртывания ✅ Документация процесса deployment
Эти проблемы и решения помогли мне построить надёжные CI/CD pipeline, которые минимизируют downtime и обеспечивают быстрое восстановление при сбоях.