diff --git a/install.sh b/install.sh index 1d9292b..0f9e24d 100644 --- a/install.sh +++ b/install.sh @@ -373,6 +373,31 @@ reload_docker_nginx() { fi } +# Ждёт HTTP /health на опубликованном порту api-gateway (127.0.0.1:3000). +wait_for_api_gateway_http() { + local max_attempts="${1:-60}" + local attempt=0 + while [[ $attempt -lt $max_attempts ]]; do + if curl -fsS --max-time 3 http://127.0.0.1:3000/health 2>/dev/null | grep -q '"status"'; then + return 0 + fi + attempt=$((attempt + 1)) + sleep 2 + done + return 1 +} + +# После recreate api-gateway nginx должен сбросить DNS/keepalive — иначе 502 на старый IP. +sync_nginx_after_api_gateway_restart() { + docker_nginx_container_running || return 0 + log "Синхронизация Docker Nginx с api-gateway после пересборки..." + wait_for_service "api-gateway" 120 || warn "api-gateway не стал healthy в срок" + wait_for_api_gateway_http 90 || warn "api-gateway /health на :3000 не отвечает" + sleep 2 + reload_docker_nginx + verify_nginx_reaches_api_gateway 24 5 || warn "Nginx → api-gateway: проверка не прошла, см. docker compose logs nginx api-gateway" +} + recreate_docker_nginx() { compose_uses_proxy_profile || return 0 build_compose_stack_cmd @@ -702,6 +727,17 @@ compose_build_and_up() { if [[ "${SKIP_DOCKER_PRUNE:-0}" -ne 1 ]]; then docker_prune_after_rebuild fi + + local svc + for svc in "${services[@]}"; do + case "$svc" in + api-gateway) sync_nginx_after_api_gateway_restart ;; + media-ws) + wait_for_service "media-ws" 60 + reload_docker_nginx + ;; + esac + done } action_docker_prune() { @@ -1944,10 +1980,12 @@ nginx_proxy_headers=" proxy_set_header Host \$host; proxy_read_timeout 300s; proxy_connect_timeout 75s;" -# Встроенный DNS Docker. В docker-режиме nginx обязан перерезолвлять имена -# бэкенд-контейнеров на каждый запрос — иначе после пересборки контейнера -# (новый IP) остаётся закешированный старый адрес и все ответы становятся 502. -NGINX_DOCKER_RESOLVER=" resolver 127.0.0.11 ipv6=off valid=1s;" +# Встроенный DNS Docker. valid=5s + resolver_timeout — баланс между свежим IP и +# «Server failure» при кратковременном дауне контейнера во время recreate. +NGINX_DOCKER_RESOLVER=" resolver 127.0.0.11 ipv6=off valid=5s; + resolver_timeout 5s;" +NGINX_DOCKER_SERVER_DNS=" resolver 127.0.0.11 ipv6=off valid=5s; + resolver_timeout 5s;" # Имя nginx-переменной из ключа апстрима (только [A-Za-z0-9_]). nginx_upstream_var() { @@ -2214,14 +2252,19 @@ ${nginx_proxy_headers} EOF } +nginx_docker_server_dns_block() { + [[ "$NGINX_MODE" == "docker" ]] && echo "$NGINX_DOCKER_SERVER_DNS" || true +} + write_nginx_site_frontend() { local domain="$1" local ssl_type="${2:-none}" - local conf http_preamble ssl_block idp_api_block proxy_root + local conf http_preamble ssl_block idp_api_block proxy_root server_dns conf="$(nginx_conf_target frontend)" proxy_root="$(render_proxy_root frontend)" http_preamble="$(nginx_http_server_preamble "$domain" "$ssl_type")" idp_api_block="$(build_nginx_idp_api_proxy_block)" + server_dns="$(nginx_docker_server_dns_block)" if [[ "$ssl_type" == "none" ]]; then nginx_write_conf "$conf" </dev/null || true)" - if [[ "$body" == *'"status":"ok"'* ]] || [[ "$body" == *'"status": "ok"'* ]]; then - ok "Docker Nginx → lendry-id-api-gateway:3000/health OK (IP api-gateway: ${gw_ip:-?})" - return 0 - fi - warn "Docker Nginx не достучался до lendry-id-api-gateway:3000 (IP: ${gw_ip:-?})" + + while [[ $attempt -lt $max_attempts ]]; do + gw_ip="$(docker_cmd inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' lendry-id-api-gateway 2>/dev/null || true)" + body="$(docker_cmd "${COMPOSE_STACK_CMD[@]}" exec -T nginx \ + wget -qO- --timeout=5 http://lendry-id-api-gateway:3000/health 2>/dev/null || true)" + if [[ "$body" == *'"status":"ok"'* ]] || [[ "$body" == *'"status": "ok"'* ]]; then + ok "Docker Nginx → lendry-id-api-gateway:3000/health OK (IP api-gateway: ${gw_ip:-?})" + return 0 + fi + attempt=$((attempt + 1)) + if [[ $attempt -lt $max_attempts ]]; then + [[ "$delay_sec" -gt 0 ]] && sleep "$delay_sec" + fi + done + + warn "Docker Nginx не достучался до lendry-id-api-gateway:3000 (IP: ${gw_ip:-?}, попыток: ${max_attempts})" warn "Проверьте: docker compose ps api-gateway && docker compose logs --tail=40 api-gateway" return 1 } @@ -2786,6 +2845,7 @@ deploy_stack() { wait_for_service "sso-core" 120 wait_for_service "api-gateway" 90 + sync_nginx_after_api_gateway_restart wait_for_service "frontend" 60 wait_for_service "docs" 60 if compose_uses_proxy_profile; then @@ -3345,6 +3405,7 @@ action_fix_host_proxy() { compose_build_and_up api-gateway wait_for_service "api-gateway" 90 wait_for_api_grpc_ready 60 || true + sync_nginx_after_api_gateway_restart ok "Прокси исправлен — проверьте: curl -I http://127.0.0.1:3002" diagnose_common_issues || true @@ -3379,6 +3440,7 @@ action_fix_all_errors() { compose_build_and_up api-gateway wait_for_service "api-gateway" 90 wait_for_api_grpc_ready 90 || compose_build_and_up api-gateway + sync_nginx_after_api_gateway_restart log "Шаг 4/6: пересборка frontend (same-origin /idp-api), docs, media-ws..." compose_build_and_up frontend docs media-ws @@ -3407,10 +3469,12 @@ action_fix_all_errors() { esac apply_intranet_selfsigned_runtime recreate_docker_nginx - verify_nginx_reaches_api_gateway || compose_build_and_up api-gateway + sync_nginx_after_api_gateway_restart + verify_nginx_reaches_api_gateway 24 5 || compose_build_and_up api-gateway + sync_nginx_after_api_gateway_restart verify_nginx_reaches_media_ws || compose_build_and_up media-ws - recreate_docker_nginx - verify_nginx_reaches_api_gateway || fail "Nginx не проксирует на api-gateway — см. docker compose logs nginx api-gateway" + reload_docker_nginx + verify_nginx_reaches_api_gateway 24 5 || fail "Nginx не проксирует на api-gateway — см. docker compose logs nginx api-gateway" check_sso_idp_api_proxy || fail "SSO /idp-api/health недоступен — см. nginx/conf.d/${NGINX_PREFIX}-frontend.conf" if ! docker_nginx_container_running; then fail "Контейнер lendry-id-nginx не запущен. Проверьте: docker compose --profile proxy logs nginx"