I have a 'discovery first' setup with Eureka, Config Server, and my client.
The issue is that these 3 services start in order, but the client-server seems to register too early, and can never find config-server. I've tried a third-party library that allows a wait until config-server:8888 is available, but that doesn't always seem to work either. It's similar to a race condition.
The workaround is that if I docker restart the client-server after everything is up, it registers and finds config-server just fine.
First run of 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
When I docker restart the client:
Fetching config from server at : http://a80b001d04a7:8888/
Located environment: name=client-server, profiles=[default], label=null, version=053c8e1b14dc0281d5af0349c9b2cf012c1a346f, state=null
Not sure if my JAVA_OPTS properties aren't being set fast enough from my docker-compose.yml, or there is some networking race condition, or what. I've been going back and forth on this for too long.
My configuration is below:
Here's my 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
Here's the 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}
Here's the config-server bootstrap.yml:
server:
port: 8888
eureka:
client:
serviceUrl:
defaultZone: ${EUREKA_SERVER:http://localhost:8761/eureka}
spring:
application:
name: config-server
Here's the client-server bootstrap.yml:
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}
Edit:
Using the docker-compose wait library (https://github.com/ufoscout/docker-compose-wait), I can have the client-server wait for eureka and config to be available, then wait 90 seconds (Eureka documentation suggests that registration could take up to 90 seconds), and it seems to work consistently.
Is this an acceptable solution? Feels like a bit of a hack.
Being purist the answer to your question is NO, it is not an acceptable solution, because as it is stated here, Docker removed healthcheck from v3 on for some reason:
Docker have made a conscious decision not to support features that wait for containers to be in a "ready" state. They argue that applications depending on other systems should be resilient to failure.
In the same link, it is described why:
The problem of waiting for a database (for example) to be ready is really just a subset of a much larger problem of distributed systems. In production, your database could become unavailable or move hosts at any time. Your application needs to be resilient to these types of failures.
To handle this, your application should attempt to re-establish a connection to the database after a failure. If the application retries the connection, it should eventually be able to connect to the database.
Basically then, there are three options:
Use v2.1 with healhcheck. See an example here
Use v3 and a tool like wait-for-it or
dockerize as #ortomala-lokni already perfectly explained
Make your application resilient to config-server failure and able config-client to retry the connection on startup
The recommended and acceptable solution is 3). You can use Spring Retry as it is mentioned here. Find below the 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 I found an error in your spring configuration. It is fail-fast and not fast-fail.
Remember to include the following dependencies (or similar if you are using gradle):
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
You can find a very well configuration (and explanation) here taking also into account resiliency during the registering process in the Eureka Server.
When having a microservices environment we must think of the resiliency of our environment when platform services like config-service, discovery-service are not available for a short period of time.
But I am not a purist at all and I would not have removed some functionality people is using (it is a question of freedom). So, an alternative solution is:
If it is working for you, then go ahead
Because I do not really understand why Docker suppressed the fantastic healthcheck command from v3.
Service dependency are always tricky when using docker-compose.
Your solution is acceptable because "there is no other way".
To avoid third-part libs, this is what I do in the same scenario:
In the Dockerfile I add netcat-openbsd, a bash file I call entrypoint and the application jar and then I run the entrypoint.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
The entrypoint file has the following instruction:
#!/bin/sh
while ! nc -z config 8888 ; do
echo "Waiting for upcoming Config Server"
sleep 2
done
java -jar /opt/lib/app.jar
It will delay the application start-up until your config server is up, without a specific interval.
The best solution is probably, as Carlos Cavero said, to make your application resilient to config-server failure. But you can also solve the problem by using the wait-for script from Eficode on Github.
Copy the script into your container and in your docker-compose.yml use:
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
The environment variables for CONFIGSERVER_SERVICE_NAME and CONFIGSERVER_PORT can be defined in your Docker Compose environment file.
If you need to wait for multiple services, you can merge this pull request and list all needed services in the command line parameters such as:
command: wait-for $SERVICE1_NAME $SERVICE1_PORT $SERVICE2_NAME $SERVICE2_PORT -- java $JVM_OPTIONS -jar client.war $SPRING_OPTIONS
Just a friendly tip: You should not bind Config to Eureka but the other way around -> Eureka should be Config client.
Related
I'm trying to deploy SpringBoot microservices using docker-compose but I'm having a problem with API Gateway.
If I run the project locally it works ok, even if I deploy project using docker-compose but API Gateway locally, it works ok, so problem has to be "dockerizing" the API Gateway service.
Doing docker logs <container> it shows:
io.netty.channel.AbstractChannel$AnnotatedConnectException: finishConnect(..) failed: Connection refused: localhost/127.0.0.1:8083
Is obvious there is a problem on host localhost/127.0.0.1. Why Gateway is trying to point a "repeated" address?.
docker-compose.yml looks like this:
version: '3.8'
services:
# more services
api-gateway:
build: ./path_to_dockerfile
depends_on:
- eureka-server
environment:
- eureka.client.serviceUrl.defaultZone=http://eureka-server:8761/eureka/
restart: always
container_name: gateway
ports:
- '9000:9000'
Dockerfile is as simple as this
FROM openjdk:8-jdk-alpine
ADD target/apigateway-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
And application.yml:
server:
port: 9000
spring:
application:
name: Api-Gateway-Service
cloud:
gateway:
default-filters:
- DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin, RETAIN_UNIQUE
globalcors:
# cors config
routes:
- id: <name>-microservice
uri: http://localhost:8083
predicates:
- Path=/<path>/**
- Method=GET,POST,DELETE,PUT,OPTIONS
# more routes on different ports
eureka:
# eureka config
So, why is adding "localhost" or "127.0.0.1" and calling twice?
Thanks in advance.
I don't think Connection refused: localhost/127.0.0.1:8083 means that it was trying to add or call localhost twice. It is just the way it shows the error.
In your application.yml, try changing uri to the name you used for your microservice inside docker-compose file.
routes:
- id: <name>-microservice
uri: <YOUR_SERVICE_NAME>
I guess the problem is that docker doesn't support network communication between containers by default. You can connect to the 8083 port from your host but not another container. If so, you should create a network and contact the container and network.
I would like to configure my dockerized spring boot application using Docker Environments. The property is a map, which I configure it in application.yml like below.
spring:
kafka:
producer:
properties:
"schema.registry.url": http://schema-registry.com:8081
I tried the following, but it didn't worked,
environment:
- SPRING_KAFKA_PRODUCER_PROPERTIES_SCHEMA.REGISTRY.URL=http://schema-registry.com:8081
How can I configure this schema.registry.url parameter from docker environment ?
Well, first of all, I would require a little bit more of information about how do you use that containerized application: Do you deploy it with docker-compose? Does it forms part of a Docker Swarm?
Depending of this, the possible solutions can vary.
Docker Swarm
For example, if you are using Docker Swarm, you can define your application.yml as a template:
application.yml.template
spring:
kafka:
producer:
properties:
"schema.registry.url": {{ env "schema_registry" }}
Then, you will have to parse that template. For that, I will suppose you have located your Spring Boot executable JAR under /usr/app in the container and that your image is named springboot-app.
docker-compose.yml
version: "3.8"
services:
springboot-app:
image: springboot-app:latest
environment:
SPRING_KAFKA_PRODUCER_PROPERTIES_SCHEMA.REGISTRY.URL: 'http://schema-registry.com:8081'
configs:
- source: springboot-app.application.yml
target: /usr/app/config/application.yaml
mode: 0440
configs:
springboot-app.application.yml:
template_driver: golang
file: ./application.yml.template
So you can deploy now your Swarm with docker stack deploy -c docker-compose.yml springboot-app.
Or even better, if you are working in a production environment, you can separate the environment variables from the common configuration:
docker-compose.yml
version: "3.8"
services:
springboot-app:
image: springboot-app:latest
configs:
- source: springboot-app.application.yml
target: /usr/app/config/application.yaml
mode: 0440
configs:
springboot-app.application.yml:
template_driver: golang
file: ./application.yml.template
docker-compose.dev.yml
version: "3.8"
services:
springboot-app:
environment:
SPRING_KAFKA_PRODUCER_PROPERTIES_SCHEMA.REGISTRY.URL: 'http://schema-registry.com:8081'
And deploy it as docker stack deploy -c docker-compose.yml -c docker-compose.dev.yml springboot-app.
Docker Compose
Since you mentioned in a lately comment that you are using docker-compose, the way of working with isn't the same.
First of all, not all the properties in Spring can be overridden in the Docker Compose file, only the ones that you can pass to Maven at the time of building or starting the application.
Also, it seems you have wrongly defined the environment Property, since normally all those that you provide you should change the dots '.' by underscores '_', but anyway, since normally the configuration of a Kafka Producer goes further than just defining an URL, I would use the profiles feature of Spring.
You can create several profiles with the configuration combinations that you want, and inform Spring via Compose which one you want to use. Let's see an example.
application.yml
spring:
config:
activate:
on-profile: "development"
kafka:
producer:
properties:
"schema.registry.url": https://kafka-dev-endpoint.com
---
spring:
config:
activate:
on-profile: "production"
kafka:
producer:
properties:
"schema.registry.url": https://kafka-prod-endpoint.com
and finally then:
docker-compose.yml
environment:
- SPRING_PROFILES_ACTIVE=development
If you wanna check further, you have more information about that here: https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto-set-active-spring-profiles
I am using the fabric8 docker-maven-plugin to build image for my Spring boot microservices.
<groupId>io.fabric8</groupId>
<artifactId>docker-maven-plugin</artifactId>
The problem is that while running the application in docker containers I have to specify the Eureka Server Container name to Eureka Client. But if I run it directly as a "Spring Boot APP" I have to use "Localhost:8761/Eureka". Is there a way to make it work both with/without docker something like given below ?
eureka:
client:
service-url:
defaultZone: ${EUREKA_SERVER:http://localhost:8761/eureka}
I am not able to pass the value of "EUREKA_SERVER" from the fabrib8 plugin. I have tried the below code to pass the value but it does not work.
<docker.env.JAVA_OPTS>-DEUREKA_SERVER=http://discovery:8761/eureka</docker.env.JAVA_OPTS>
Spring can pickup Environment Variables. So if you add Environment Variables to the Docker Container that Spring Boot is running in, they will work. This avoids the need to provide a static URL up front.
If you use Docker Compose, it could look like this:
services:
eureka:
image: springcloud/eureka
container_name: eureka
ports:
- "8761:8761"
networks:
- "discovery"
environment:
- EUREKA_INSTANCE_PREFERIPADDRESS=true
spring:
build:
context: .
dockerfile: ./src/main/docker/Dockerfile
depends_on:
- eureka
container_name: spring
ports:
- "8080:8080"
networks:
- "discovery"
environment:
- EUREKA_SERVICE_URL=http://eureka:8761 // This overrides your Spring Property
- EUREKA_INSTANCE_PREFER_IP_ADDRESS=true
- LOGGING_FILE=/tmp/admin.log
Note: Since Environment Variables are not YAML, you need to change the format a bit. https://docs.spring.io/spring-boot/docs/1.5.5.RELEASE/reference/html/boot-features-external-config.html#boot-features-external-config-relaxed-binding
I'm setting an environment variable inside my docker-compose.yaml file and want to use that variable's value inside my Spring Boot's application.yaml. I was told that doing something like
app:
auth:
tokenSecret: tokensecretvaluehere
tokenExpirationMsec: 864000000
oauth2:
sso:
url: ${SSO_URL}
(where SSO_URL is defined in my docker-compose.yaml) in my Spring application.yaml. However, this causes an error when I run docker-compose up --build because it can't find that variable (error is like: Could not resolve placeholder SSO_URL in value "${SSO_URL}"). This is an example of what my docker-compose.yaml:
api:
restart: always
ports:
- "8080:8080"
links:
- redis
- db
environment:
- SERVER_SERVLET_SESSION_COOKIE_DOMAIN=localhost
- SSO_URL=myvaluehere
I was asked to not uses the System.getenv functions in Java and instead set the variable like above. From there I would just use the #Value annotation to get it in my Java code as like below:
#Value("${app.oauth2.sso.url}")
private String ssoUrl;
This is more of the application.yaml:
heb:
togglr:
jwt:
secret:
id: 101
session:
seconds: 600
tokenheader: X-TOGGLR-TOKEN
logging:
level:
com:
heb: debug
default: debug
path: logs
server:
error:
whitelabel:
enabled: false
port: 8080
servlet:
context-path: /togglr-api
use-forward-headers: true
spring:
application:
name: togglr_api
freemarker:
enabled: false
groovy:
template:
enabled: false
jmx:
enabled: false
main:
banner-mode: 'off'
thymeleaf:
cache: false
security:
oauth2:
client:
registration:
github:
clientId:
clientSecret:
redirectUri:
scope:
- user:email
- read:user
app:
auth:
tokenSecret:
tokenExpirationMsec: 864000000
oauth2:
sso:
url: ${SSO_URL}
In general spring boot applications are able to read the Environment variables accessible in the docker container. Its stated in the documentation (see item 10 in the list at the very beginning of the document).
So the problem might be elsewhere:
It might be a typo in the question, but if you're using application.yaml as opposed to application properties, then you should have something like:
sso:
url: ${SSO_URL}
Make sure, that the env variable SSO_URL is indeed accessible in the container even before you start the spring boot application. In java (for debugging purposes only) you can do something like:
#SpringBootApplication
public class MyApp {
public static void main(String [] args) {
System.out.println(System.getenv("SSO_URL"));
SpringApplication.run(MyApp.class);
}
}
Solution was to not use an underscore character in the variable name.
I feel what you are missing is the build context within the docker-compose file. Or, you have multiple profile based application.yml and not the correct profile is being set.
Below is the working code / config. So we are creating a springboot application from docker-compose, where docker-compose builds the image of springboot application and passes the required environment variables.
Snippet from Spring application.yml where we are using the env variable named API_BASE and TEST_API_PATH
third-party-api:
base-url: ${API_BASE}
test-api-path: ${TEST_API_PATH}
Below is the snippet from docker-compose.yml
my-app:
image: my-app-image-name
build:
dockerfile: Dockerfile
context: .
ports:
- '9080:8080'
environment:
API_BASE: http://mock-api:8080
TEST_API_PATH: /someTestApiPath
SPRING_PROFILES_ACTIVE: dev
Docker file of my application is pretty simple.
FROM openjdk:17-alpine
ARG JAR_FILE=build/libs/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
Dockerfile and docker-compose.yml are on the same hierarcy, if there is any difference in your structure then it should reflect in below config of docker-compose.yml
build:
dockerfile: Dockerfile
I'm trying to use docker-compose to run 2 simple services locally (Ubuntu): a eureka server, and config server (which is also a eureka client). Both of these have simple dockerfiles that run java -jar, expose their ports, and individually work fine. I also tried to add eureka.client.service-url.defaultZone=http://company-service-discovery:8761/eureka to see if it would register itself, and it worked.
My config server cannot successfully register to the eureka server, and I've googled it and nothing I've seen helped me solve this.
According to docker-compose documentation at https://docs.docker.com/compose/networking/ :
By default Compose sets up a single network for your app. Each container for a service joins the default network and is both reachable by other containers on that network, and discoverable by them at a hostname identical to the container name.
With the following example web should be able to use postgres://db:5432 to communicate with the database.
version: "3"
services:
web:
build: .
ports:
- "8000:8000"
db:
image: postgres
ports:
- "8001:5432"
I have used the same method to configure my services, but my config server gets connection refused when trying to register:
docker-compose.yml
version: '3.3'
services:
company-service-discovery:
build: company-service-discovery/
ports:
- "8761:8761"
company-config-server:
build: company-config-server/
ports:
- "8888:8888"
links:
- company-service-discovery
config server bootstrap.yml
server:
port: 8888
management:
security:
enabled: false
spring:
application:
name: company-config-server
cloud:
config:
server:
native:
search-locations: classpath:/shared
profiles:
include: native
eureka:
client:
service-url:
defaultZone: http://company-service-discovery:8761/eureka
eureka server bootstrap.yml
spring:
application:
name: company-service-discovery
server:
port: 8761
management:
security:
enabled: false
exception
2017-07-26 14:25:05.738 WARN 1 --- [nfoReplicator-0] c.n.d.s.t.d.RetryableEurekaHttpClient : Request execution failed with message: java.net.ConnectException: Connection refused (Connection refused)
2017-07-26 14:25:05.739 WARN 1 --- [nfoReplicator-0] com.netflix.discovery.DiscoveryClient : DiscoveryClient_COMPANY-CONFIG-SERVER/365d20275ab0:company-config-server:8888 - registration failed Cannot execute request on any known server
question
Is there anything wrong with my configuration ? How can I make it work ?
Let me know if there's any info missing, I'll gladly give any info I can.
Add the defaultZone configuration to the Eureka server's properties too (and change the service-url to serviceUrl in your config server bootstrap.yml).
eureka server bootstrap.yml
spring:
application:
name: company-service-discovery
eureka:
client:
serviceUrl:
defaultZone: http://company-service-discovery:8761/eureka
server:
port: 8761
management:
security:
enabled: false
Thank you sooo much. this post resolved my problem - connecting eureka client to eureka server with docker. After 2 days of searching it work. I am in tears.
So basically -
you should use following in eureka client's application.properties/.yml
eureka.client.service-url.defaultZone=http://eureka-server:8761/eureka
and in docker-compose.yml your eureka service name should match with - the url host name, in my case it is - eureka-server