web-dev-qa-db-fra.com

Le serveur Spring Config n'est pas accessible avec docker-compose jusqu'au redémarrage du client

J'ai une configuration "découverte d'abord" avec Eureka, Config Server et mon client.

Le problème est que ces 3 services démarrent dans l'ordre, mais le client-serveur semble s'enregistrer trop tôt et ne peut jamais trouver config-server. J'ai essayé une bibliothèque tierce qui permet d'attendre jusqu'à ce que config-server: 8888 soit disponible, mais cela ne semble pas toujours fonctionner non plus. C'est semblable à une condition de course.

La solution de contournement est que si je docker restart le client-serveur une fois que tout est en place, il s'enregistre et trouve très bien config-server.

Première exécution de docker-compose:

Fetching config from server at : http://localhost:8888
Connect Timeout Exception on Url - http://localhost:8888. Will be trying the next url if available

Quand je docker restart le client:

Fetching config from server at : http://a80b001d04a7:8888/
Located environment: name=client-server, profiles=[default], label=null, version=053c8e1b14dc0281d5af0349c9b2cf012c1a346f, state=null

Je ne sais pas si mes propriétés Java_OPTS ne sont pas définies assez rapidement à partir de mon docker-compose.yml, ou s'il y a une condition de concurrence réseau, ou quoi. Je fais des allers-retours depuis trop longtemps.

Ma configuration est ci-dessous:

Voici mon docker-compose.yml:

version: '3'
services:
  eureka:
    image: eureka-server:latest
    environment:
    - "Java_OPTS=-DEUREKA_SERVER=http://eureka:8761/eureka"
    ports:
      - 8761:8761
  config:
    image: config-server:latest
    environment:
      - "Java_OPTS=-DEUREKA_SERVER=http://eureka:8761/eureka"
    depends_on:
      - eureka
    ports:
      - 8888:8888
  client:
    image: client-server:latest
    environment:
      Java_OPTS: -DEUREKA_SERVER=http://eureka:8761/eureka
    depends_on:
      - config
    ports:
      - 9000:9000

Voici le eureka-server application.yml:

server:
  port: 8761

spring:
  application:
    name: eureka-server

eureka:
  client:
    registerWithEureka: false
    fetchRegistry: false
    service-url:
      defaultZone: ${EUREKA_SERVER:http://localhost:8761/eureka}

Voici le bootstrap.yml du serveur de configuration:

server:
  port: 8888

eureka:
  client:
    serviceUrl:
      defaultZone: ${EUREKA_SERVER:http://localhost:8761/eureka}

spring:
  application:
    name: config-server

Voici le bootstrap.yml client-serveur:

spring:
  application:
    name: client-server
  cloud:
    config:
      discovery:
        enabled: true
        serviceId: config-server
      fast-fail: true
    retry:
      max-attempts: 10000
      max-interval: 1000

eureka:
  instance:
    hostname: client-server
  client:
    registerWithEureka: true
    fetchRegistry: true
    serviceUrl:
      defaultZone: ${EUREKA_SERVER:http://localhost:8761/eureka}

Éditer:

En utilisant la bibliothèque d'attente docker-compose ( https://github.com/ufoscout/docker-compose-wait ), je peux faire attendre le client-serveur pour que eureka et config soient disponibles, puis attendre 90 secondes (la documentation d'Eureka suggère que l'enregistrement pourrait prendre jusqu'à 90 secondes), et cela semble fonctionner de manière cohérente.

Est-ce une solution acceptable? On dirait un peu un hack.

8
thedude19

Étant puriste, la réponse à votre question est [~ # ~] non [~ # ~] , ce n'est pas une solution acceptable, car comme il est indiqué ici , Docker a supprimé healthcheck de la v3 pour une raison quelconque:

Docker a pris la décision consciente de ne pas prendre en charge les fonctionnalités qui attendent que les conteneurs soient dans un état "prêt". Ils soutiennent que les applications dépendantes d'autres systèmes devraient être résistantes aux pannes.

Dans le même lien, il est expliqué pourquoi:

Le problème d'attendre qu'une base de données (par exemple) soit prête n'est vraiment qu'un sous-ensemble d'un problème beaucoup plus important de systèmes distribués. En production, votre base de données peut devenir indisponible ou déplacer des hôtes à tout moment. Votre application doit être résiliente à ces types d'échecs.

Pour gérer cela, votre application doit tenter de rétablir une connexion à la base de données après un échec. Si l'application réessaye la connexion, elle devrait éventuellement pouvoir se connecter à la base de données.

Fondamentalement, il existe trois options:

  1. Utilisez la v2.1 avec healhcheck. Voir un exemple ici
  2. Utilisez v3 et un outil comme wait-for-it ou dockerize comme @ ortomala-lokni déjà parfaitement expliqué
  3. Rendez votre application résistante aux pannes du serveur de configuration et config-client capable de réessayer la connexion au démarrage

La solution recommandée et acceptable est 3). Vous pouvez utiliser Spring Retry comme il est mentionné ici . Trouvez ci-dessous le bootstrap.yml configuration:

spring:
  application:
    name: config-client
  profiles:
     active: dev
  cloud:
    config:
     discovery:
       enabled: true
       service-id: config-server
     fail-fast: true
     retry:
       initial-interval: 1500
       multiplier: 1.5
       max-attempts: 10000
       max-interval: 1000

eureka:
  instance:
    hostname: config-client
  client:
    registerWithEureka: true
    fetchRegistry: true
    serviceUrl:
      defaultZone: ${EUREKA_SERVER:http://localhost:8761/eureka}

BTW J'ai trouvé une erreur dans votre configuration de ressort. C'est fail-fast et pas fast-fail.

N'oubliez pas d'inclure les dépendances suivantes (ou similaires si vous utilisez gradle):

<dependency>
    <groupId>org.springframework.retry</groupId>
    <artifactId>spring-retry</artifactId>
</dependency>

Vous pouvez trouver une très bonne configuration (et explication) ici prenant également en compte la résilience lors du processus d'enregistrement sur le serveur Eureka.

Lorsque nous avons un environnement de microservices, nous devons penser à la résilience de notre environnement lorsque les services de plate-forme tels que le service de configuration, le service de découverte ne sont pas disponibles pendant une courte période.

Mais je ne suis pas du tout puriste et je n'aurais pas supprimé certaines fonctionnalités que les gens utilisent (c'est une question de liberté). Ainsi, une solution alternative est:

Si cela fonctionne pour vous, alors allez-y

Parce que je ne comprends pas vraiment pourquoi Docker a supprimé la fantastique commande healthcheck de la v3.

3
Carlos Cavero

La meilleure solution est probablement, comme l'a dit Carlos Cavero , de rendre votre application résistante aux pannes du serveur de configuration. Mais vous pouvez également résoudre le problème en utilisant wait-for script de Eficode sur Github.

Copiez le script dans votre conteneur et dans votre docker-compose.yml utilisation:

client:
    image: client-server:latest
    environment:
      Java_OPTS: -DEUREKA_SERVER=http://eureka:8761/eureka
    depends_on:
      - config
    ports:
      - 9000:9000
    command: wait-for $CONFIGSERVER_SERVICE_NAME:$CONFIGSERVER_PORT -- Java $JVM_OPTIONS -jar client.war $SPRING_OPTIONS

Les variables d'environnement pour CONFIGSERVER_SERVICE_NAME et CONFIGSERVER_PORT peut être défini dans votre fichier d'environnement Docker Compose .

Si vous devez attendre plusieurs services, vous pouvez fusionner cette demande d'extraction et répertorier tous les services nécessaires dans les paramètres de ligne de commande tels que:

command: wait-for $SERVICE1_NAME $SERVICE1_PORT $SERVICE2_NAME $SERVICE2_PORT -- Java $JVM_OPTIONS -jar client.war $SPRING_OPTIONS
3
Ortomala Lokni

La dépendance aux services est toujours délicate lors de l'utilisation de docker-compose.

Votre solution est acceptable car "il n'y a pas d'autre moyen". Pour éviter les bibliothèques tierces, voici ce que je fais dans le même scénario:

Dans le Dockerfile j'ajoute netcat-openbsd, un fichier bash que j'appelle entrypoint et le pot d'application, puis j'exécute le point d'entrée.sh.

FROM openjdk:8-jdk-Alpine
RUN apk --no-cache add netcat-openbsd
COPY entrypoint.sh /opt/bin/
COPY app.jar /opt/lib/
RUN chmod 755 /opt/esusab-bi/bin/app/entrypoint.sh

Le fichier de point d'entrée contient les instructions suivantes:

#!/bin/sh

while ! nc -z config 8888 ; do
    echo "Waiting for upcoming Config Server"
    sleep 2
done

Java -jar /opt/lib/app.jar

Cela retardera le démarrage de l'application jusqu'à ce que votre serveur de configuration soit opérationnel, sans intervalle spécifique.

2
Mateus