Leveraging Alibaba Cloud Container Service to develop Spring Cloud Microservice Application (1)

This article describes how to use Eureka for service registration and discovery as well as how to create images for all services in the sample code.

Traditional applications contain a fixed number of services and service nodes are relatively fixed, so dynamic discovery is not required. Usually, service discovery finds services through DNS resolution or IP addresses. In cloud environments, the service quantity may change at any time, and service and listening nodes are unknown. Therefore, in cloud application microservice architectures, service discovery is an essential function.
You can use ZooKeeper, etcd, Consul, or other open source products for service registration and discovery. Spring Cloud provides two methods: Consul and Netflix's Eureka. This article will discuss the use of Eureka for service discovery and ways to containerize it.

Eureka Service Discovery and Image Creation

Eureka servers are generated through annotation statements in the program. If you want to use an existing image, use the following example:
docker pull registry.aliyuncs.com/jingshanlb/discovery-server

Create Eureka server image
If you already have an image, you can skip this step.
Introduce Eureka dependencies
Introduce Java project Eureka dependencies into build.gradle.

...  dependencies {      compile('org.springframework.cloud:spring-cloud-starter-eureka-server')      ...  }  ... 

Use annotations to declare Eureka server
Declare Eureka servers by adding annotations to the program. The following functions are called:

@SpringBootApplication  @EnableEurekaServer  @EnableDiscoveryClient  public class DiscoveryServerApplication {       public static void main(String[] args) {          SpringApplication.run(DiscoveryServerApplication.class, args);      }  } 

Configure the Eureka server
In application.yml, configure the Eureka parameters using the following details:

server:    port: 8761   eureka:    instance:      hostname: localhost    client:      registerWithEureka: false      fetchRegistry: false      serviceUrl:        defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/   ---  spring:    profiles: cloud   server:    port: 8761   eureka:    instance:      hostname: localhost    client:      registerWithEureka: false      fetchRegistry: false      serviceUrl:        defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/,${ADDITIONAL_EUREKA_SERVER_LIST} 

In the test environment (profile not specified), the Eureka listening port is 8761 and serviceUrl.defaultZone only contains one Eureka service instance.
In subsequent cloud profiles, in addition to this Eureka server, serviceUrl.defaultZone will use the ADDITIONAL_EUREKA_SERVER_LIST environment variable to specify the Eureka server node information. Content is synchronized between Eureka servers, and Eureka clients only have to register with a single node.

Start Eureka server
There are two ways to run the Eureka service: 1. Execute the command using the Docker command line; or 2. Declare it in docker-compose file and start docker-compose.
The command line method can be executed from gradle.
gradle bootRun
Or, you can use a Java command to start a single Eureka server. This achieves the same result as the gradle command.
java -jar build/libs/discovery-server-0.0.1-SNAPSHOT.jar
Use a Java command to start a Eureka server with the profile set to cloud, and use the environment variable to specify the addresses of other nodes in the Eureka cluster.
ADDITIONAL_EUREKA_SERVER_LIST=http://discovery2:8761/eureka/ SPRING_PROFILES_ACTIVE=cloud java -jar build/libs/discovery-server-0.0.1-SNAPSHOT.jar
Since you often have to start multiple containers when developing actual applications, using a script to initiate the service is highly inconvenient. Therefore, it is recommended that you declare the following environment variable for container startup in the docker-compose file to configure the Eureka startup behavior:

services:    discovery1:      container_name: discovery1      image: registry.aliyuncs.com/jingshanlb/discovery-server      ... ...      environment:        - ADDITIONAL_EUREKA_SERVER_LIST=http://discovery2:8761/eureka/        - SPRING_PROFILES_ACTIVE=cloud 
Multiple Eureka servers must be specified in the ADDITIONAL_EUREKA_SERVER_LIST environment variable. Separate the URLs for each server with commas.

Create Eureka server image
The Jar packages compiled by the springboot application contain all the dependency packages and app servers. This package is also called an Uber Jar. Compiled Jar packages are specified in the docker-compose file for the docker engine to generate container images.
We will not discuss how to compile Uber Jars in detail. However, remember to execute the following command:
gradle build
Compiled Jar packages are saved in the build/libs directory:

 $ tree build/libs/  build/libs/  ├── discovery-server-0.0.1-SNAPSHOT.jar  └── discovery-server-0.0.1-SNAPSHOT.jar.original   0 directories, 2 files  

Write a Dockerfile that defines the Eureka server image generation process:
 FROM java:8-jre-alpine  RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/' /etc/apk/repositories  VOLUME /tmp  ADD build/libs/*.jar app.jar  RUN sh -c 'touch /app.jar'  RUN echo $(date) > /image_built_at  ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar","--spring.profiles.active=${SPRING_PROFILES_ACTIVE}"] 

ADD build/libs/*.jar app.jar indicates that the created Jar package is copied to the image. The build/libs directory has only one Jar package, so this command will copy the Jar package to the image root directory and name it app.jar. This Dockerfile can be used for all the user's compiled springboot applications.
RUN echo $(date) > /image_built_at indicates that the image creation date is added to the image to facilitate future O&M. You can modify or delete this line as needed.
Run the following command to generate an image. Then, upload it to your image warehouse account:

#Java construction  gradle build   #Create an image  export REGPREFIX=registry.aliyuncs.com// docker tag $(docker build -t ${REGPREFIX}discovery-server -q .) ${REGPREFIX}discovery-server:$(date -ju "+%Y%m%d-%H%M%S")  
Above command generates a Eureka server image with two tags: latest and creation time.
For example: 20160627-092112. You can define necessary tags to meet your needs.
Log on to the image warehouse, and execute the following upload image command:
#Log on to the image warehouse, with the first *** replaced by your username and the second *** replaced by your password  docker login -u *** -p *** registry.aliyuncs.com   #Upload the created image to the image warehouse, with  replaced by your username  export REGPREFIX=registry.aliyuncs.com// docker push {REGPREFIX}discovery-server  
Note: If this is your first image upload, you must create an image warehouse on the Alibaba Cloud Developer Platform. Otherwise, the image upload will fail. Go to the "Create Image Warehouse" page. Enter a warehouse name and select Local Warehouse.

After creating a warehouse, upload images at any time.
Register and discover services through Eureka Client
Except for the Eureka server, all registration-required applications must be registered with Eureka Server through Eureka Client. Eureka Client also obtains access information for other applications.
Annotate
To introduce Eureka Client, use the annotation mechanism:

@SpringBootApplication  @EnableDiscoveryClient  public class BarApplication {       public static void main(String[] args) {          SpringApplication.run(BarApplication.class, args);      }  }  
@EnableDiscoveryClient has the same annotation function as that previously described about Eureka Server. However, Eureka Client cannot declare EnableEurekaServer.
Define names for the applications in bootstrap.yml. The applications are registered with Eureka using its name. All applications registered with the same name are regarded as different instances of the same application (or service). Therefore, make sure that application names are contextually unique within a Eureka cluster. spring:
application:
name: bar

Eureka Client applications use the build.gradle file to introduce Eureka dependencies. The file application.yml configures Eureka. The compilation, running, and image creation of an application are similar to those on Eureka Server.
Discover and call services
With the spring.application.name statement declared in the bootstrap.yml file, the application automatically registers with Eureka Server at startup. How can we discover the service? Look at the foobar code.
... @Autowired      @LoadBalanced      private RestTemplate restTemplate;   ...      private BarMessage getMessageFromBarService(){          BarMessage bar = restTemplate.getForObject("http://bar/message", BarMessage.class);          log.debug("From bar service : {}.", bar);          return bar;      }  ...  
The @Autowired and @LoadBalanced annotations are added to the RestTemplate statement. Calling restTemplate.getForObject retrieves the service URL.
http://bar/message
Bar is the service name and /message is replaced with the URL path to call. restTemplate.getForObject retrieves all bar service instances from Eureka, selects an instance to call, and returns the result based on the built-in load balancing algorithm.
You can use RestTemplate to discover and call a service. Spring Cloud also provides other methods, such as Feign, but we will not describe them now.

Sample Code

Find the sample code used in the aliyuncs-springcloud-demo series at: https://code.aliyun.com/libin.libin/aliyuncs-springcloud-demo.
In the aliyuncs-springcloud-demo directory, you will find the following content:

aliyuncs-springcloud-demo$ tree -L 2  .  ├── build-all.sh  ├── clean-all-images.sh  ├── common  │   ├── discovery-server  │   └── gateway  ├── docker-compose.acs.yml  ├── docker-compose.yml  ├── push-all-images.sh  └── services      ├── bar      ├── foo      └── foobar  
The common directory contains public services, including the Discovery Server (Eureka) and Gateway (Zuul) intelligent routing service.
The services directory contains all the services with application business logic. These services are all registered with Discovery Server, which also performs service discovery.
We have already discussed the registration and discovery mechanisms. The Docker image creation process for all applications (including Discovery Server and Gateway) is almost identical. You can find relevant files in the respective directories.

Compile all services and upload images
Manually compiling services and uploading images in each directory is cumbersome and error-prone. We use a script to create and upload images for all services.
Run the following command to compile and bundle all services. Then, create, but not upload, container images:

#Replace jingshanlb with your actual username before executing the command 
./build-all.sh
Run the following command to upload all images (if an image warehouse is not yet created, follow the instructions in the Eureka Server section to create one):
#Replace jingshanlb with your actual username before executing the command 
./push-all-images.sh

Conclusion

This article describes how to use Eureka to register and discover services and create images. Finally, we briefly described how to create images for all the services in the sample code.
In future articles, we will look at some other features of the Spring Cloud and Alibaba Cloud Container Service.