HƯỚNG DẪN CÁC BƯỚC PHÁT TRIỂN HỆ THỐNG MICROSERVICE VỚI DOCKER
17/06/2020 2259

Trước khi Microservice ra đời, chúng ta phải thường xuyên làm việc với kiến trúc nguyên khối – Monolith. Một vấn đề phổ biến với Monolith là khi có bất kỳ thay đổi nhỏ nào, nhà phát triển cũng phải tốn công sức xây dựng và triển khai lại toàn bộ ứng dụng. Tuy nhiên, vấn đề này sẽ được giải quyết triệt để với Microservice. Vậy Microservice là gì? Cách xây dựng hệ thống Microservice gồm các bước nào? Hãy cùng chúng tôi khám phá trong bài viết này nhé!
1. Giới thiệu về Microservice
Microservices là một giải pháp kiến trúc cho phép chia một hệ thống lớn thành một vài các component độc lập về phát triển, test và deploy. Một ứng dụng được phát triển dựa trên kiến trúc microservice bao gồm các hệ thống con được phân tán, tức là các phần của ứng dụng đó có thể được phát triển trên các ngôn ngữ khác nhau và được triển khai trên các máy chủ khác nhau.
2. Kiến trúc hệ thống của Microservice
Để hiểu rõ về Microservices, chúng ta sẽ khảo sát hệ thống Microservice sau:
Như sơ đồ kiến trúc trên, ta có thể thấy kiến trúc của hệ thống Microservice có thành phần chính là service registry, các service và Gateway.
1. Service registry:
Nơi để lưu trữ thông tin của các service là số lượng được triển khai, phiên bản, tình trạng deploy, … Mỗi service được định danh bởi một tên gọi cụ thể để các service có thể gọi lẫn nhau dễ dàng khi mà mỗi lần deploy IP động của service thay đổi.
2. Service:
Mỗi client service chỉ thực hiện duy nhất một nghiệp vụ nào đó trong hệ thống như thanh toán, tài khoản, thông báo, xác thực, cấu hình, … Mỗi service là độc lập trong kiến trúc Microservice.
3. Gateway – Zuul:
Gateway là gì?
Khi chúng ta gọi đến bất kỳ service nào từ browser, chúng ta không thể gọi trực tiếp bằng tên của chúng như ở trên Team server đã làm bởi vì những tên service như vậy phải được bí mật và chỉ sử dụng trong nội bộ các service với nhau.
Nếu chúng ta có nhiều instance của một service, mỗi instance lại sử dụng một port khác nhau. Vậy làm thế nào chúng ta có thể gọi tất cả các service từ browser và phân tán những request đến các instance đó thông qua các cổng khác nhau? Câu trả lời là sử dụng một Gateway.
Một gateway là một entry point đơn trong hệ thống, được dùng để handle các request bằng cách định tuyến chúng đến các service tương ứng. Nó cũng có thể được dùng để xác thực, giám sát và làm nhiều việc khác.
Zuul là gì?
Nó là một proxy, gateway và một lớp trung gian giữa user và các service của bạn. Eureka server đã giải quyết vấn đề đặt tên cho từng service thay vì dùng địa chỉ IP của chúng. Tuy nhiên một service vẫn có thể có nhiều instance và chúng sẽ chạy trên các cổng khác nhau. Do đó, nhiệm vụ của Zuul sẽ là:
- Map giữa một prefix path và một service (team-service). Nó sử dụng Eureka server để định tuyến các service được request.
- Giúp cân bằng tải giữa các instance của một service.
- Filter request, thêm xác thực,…
Như vậy, chúng ta đã dựng xong bộ khung cho hệ thống microservice, gồm một service discovery (Eureka server), hai service (Member và Team), một cổng gateway (Zuul).
3. Bài toán thực tế với Microservice
Để có thể hình dung rõ hơn các bước phát triển một hệ thống sử dụng kiến trúc Microservice, chúng ta sẽ thực hành giải quyết một bài toán liên quan đến việc quản lý nhân sự trong công ty. Ví dụ: chúng ta cần lấy thông tin nhân viên để xác định họ thuộc bộ phận nào? Bộ phận đó có bao nhiêu thành viên, gồm những ai…
Bước 1:
Môi trường / công cụ cần chuẩn bị:
- Java(JDK 1.8 hoặc phiên bản mới hơn)
- Maven 3.x
- Framework: Spring Boot
- IDE: Spring Tool Suite (STS) hoặc eclipse
Sẽ tạo 1 Eureka Server, 1 Zuul (Gateway), 2 Service (Member, Team)
Bước 2:
Truy cập Link sau để tạo Project
Tạo Project: Eureka Server
Thiết lập như cấu hình trong ảnh sau đó ấn GENERATE
Tạo Project: Zuul Service như cấu hình sau:
Tạo Project: Team Service như cấu hình sau:
Tạo Project: Member Service như cấu hình sau:
Sau khi tải về ta có các file như sau:
Giải nén ra:
Bật IDE import tất cả project trên vào
Bước 3:
Sửa code trong các Project
Project spring-eureka-server:
Chúng ta sẽ khai báo đây là một Eureka server bằng annotation @EnableEurekaServer
Các bạn sửa file sau thành: spring-eureka-server\src\main\java\com\eureka\server\main\SpringEurekaServerApplication.java
package com.eureka.server.main;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;
@EnableEurekaServer
@SpringBootApplication
public class SpringEurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(SpringEurekaServerApplication.class, args);
}
}
Tiếp theo sửa file: spring-eureka-server\src\main\resources\application.properties
# Give a name to the eureka server
spring.application.name=eureka-server
# default port for eureka server
server.port=8761
# eureka by default will register itself as a client. So, we need to set it to false.
# What's a client server? See other microservices (student, auth, etc).
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
Với cấu hình này eureka-server sẽ chạy trên port 8761
Project spring-eureka-zuul:
Chúng ta sẽ khai báo đây là một Eureka client và Zuul bằng annotation @EnableEurekaClient, @EnableZuulProxy
Các bạn sửa file sau: spring-eureka-zuul\src\main\java\com\eureka\zuul\main\SpringEurekaZuulApplication.java
package com.eureka.zuul.main;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@SpringBootApplication
@EnableEurekaClient
@EnableZuulProxy
public class SpringEurekaZuulApplication {
public static void main(String[] args) {
SpringApplication.run(SpringEurekaZuulApplication.class, args);
}
}
Vì zuul là Gateway nên khi truy xuất dữ liệu vào Member hoặc Team thì sẽ phải gọi qua Zuul.
Để định nghĩa các service trong hệ thống của mình, cụ thể trong bài này có 2 service là Member và Team, chúng ta phải khai path và service-id như sau:
zuul.routes.member-service.path=/member/**
zuul.routes.member-service.service-id=member-service
zuul.routes.team-service.path=/team/**
zuul.routes.team-service.service-id=team-service
Cụ thể, sửa file với nội dung sau: spring-eureka-zuul\src\main\resources\application.properties
server.port=8762
spring.application.name=zuul-server
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
# A prefix that can added to beginning of all requests.
#zuul.prefix=/api
# Disable accessing services using service name (i.e. gallery-service).
# They should be only accessed through the path defined below.
# Link: https://stackoverflow.com/questions/46317388/zuul-service-name-exposed-instead-of-route-path-only
zuul.ignored-services=*
zuul.routes.member-service.path=/member/**
zuul.routes.member-service.service-id=member-service
zuul.routes.team-service.path=/team/**
zuul.routes.team-service.service-id=team-service
# Exclude authorization from sensitive headers
zuul.routes.auth-service.sensitive-headers=Cookie,Set-Cookie
zuul.ignored-headers=Access-Control-Allow-Credentials, Access-Control-Allow-Origin
Với Project spring-eureka-team:
Các bạn sửa file sau thành: spring-eureka-team\src\main\java\com\eureka\team\main\ SpringEurekaTeamApplication.java
package com.eureka.team.main;
import java.util.Arrays;
import java.util.List;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@EnableEurekaClient
public class SpringEurekaTeamApplication {
public static void main(String[] args) {
SpringApplication.run(SpringEurekaTeamApplication.class, args);
}
}
@RestController
class TeamRestController {
@RequestMapping(value = "list", method = RequestMethod.GET, produces = "application/json")
public List<Team> getAll() {
List<Team> teams = Arrays.asList(
new Team(1, "Team 1"),
new Team(2, "Team 2"),
new Team(3, "Team 3"));
return teams;
}
}
class Team {
private Integer id;
private String name;
public Team(Integer id, String name) {
this.id = id;
this.name = name;
}
public Team() {
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Tiếp theo sửa file: spring-eureka-team\src\main\resources\application.properties
spring.application.name=team-service
server.port=8201
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka
Với Project spring-eureka-member:
Các bạn sửa file sau thành: spring-eureka-member\src\main\java\com\eureka\member\main\ SpringEurekaMemberApplication
package com.eureka.member.main;
import java.util.Arrays;
import java.util.List;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@EnableEurekaClient
public class SpringEurekaMemberApplication {
public static void main(String[] args) {
SpringApplication.run(SpringEurekaMemberApplication.class, args);
}
}
@RestController
class MemberRestController {
@RequestMapping(value = "list", method = RequestMethod.GET, produces = "application/json")
public List<Member> getMembers() {
List<Member> members = Arrays.asList(
new Member(1, "Nguyen Van A "),
new Member(2, "Nguyen Van B "),
new Member(3, "Nguyen Van C "));
return members;
}
}
class Member {
private Integer id;
private String name;
public Member(Integer id, String name) {
this.id = id;
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Tiếp theo sửa file: spring-eureka-team\src\main\resources\application.properties
spring.application.name=member-service
server.port=8202
eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka
Bước 4:
Chạy lần lượt các service: spring-eureka-server, spring-eureka-zuul, spring-eureka-team, spring-eureka-member
Để kiểm tra ứng dụng, hãy truy cập http://localhost:8761, đây là cổng của Eureka Server. Và bạn sẽ thấy các service đang chạy như sau:
Tiếp theo chúng ta gọi đến service Member thông qua Zuul bằng đường dẫn
localhost:8762/member/list
Kết quả nhận được sẽ là danh sách Member:
Truy cập tiếp đường dẫn: http://localhost:8762/team/list
Kết quả:
Vậy là chúng ta đã xây dựng xong hệ thống microservice.
4. Ứng dụng Docker trong hệ thống Microservice
Điều gì sẽ xảy ra nếu như bạn không chỉ có một mà có đến hàng chục hoặc hàng trăm serive cần triển khai? Việc triển khai thủ công sẽ cực kỳ tốn thời gian. Câu trả lời cho bài toán này chính là ứng dụng Docker. Khi sử dụng Docker, chúng ta có thể dễ dàng tự động hóa việc deploy cho tất cả các service:
Các bước ứng dụng Docker như sau:
Bước 1:
Tạo file docker-compose.yml để ngang hàng với thư mục các Project java với nội dung:
version : "2.1"
services:
eureka:
image: microservice/server
container_name: eureka
build:
context: .
dockerfile: ./spring-eureka-server/Dockerfile
hostname: eureka
ports:
- "8761:8761"
networks:
- microservicesnet
zuul:
image: microservice/zuul
container_name: zuul
build:
context: .
dockerfile: ./spring-eureka-zuul/Dockerfile
ports:
- "8762:8762"
networks:
- microservicesnet
team:
image: microservice/team
container_name: team
build:
context: .
dockerfile: ./spring-eureka-team/Dockerfile
ports:
- "8201:8201"
networks:
- microservicesnet
member:
image: microservice/member
container_name: member
build:
context: .
dockerfile: ./spring-eureka-member/Dockerfile
ports:
- "8202:8202"
networks:
- microservicesnet
networks:
microservicesnet:
Bước 2:
Lần lượt tạo Dockerfile cho spring-eureka-server, spring-eureka-zuul, spring-eureka-team, spring-eureka-member. Đặt Dockerfile vào trong mỗi project.
Project spring-eureka-server:
FROM java:8
FROM maven:alpine
# image layer
WORKDIR /app/spring-eureka-server
# Image layer: with the application
COPY ./spring-eureka-server /app/spring-eureka-server
RUN mvn -v
RUN mvn clean install -DskipTests
ENTRYPOINT ["java","-jar","/app/spring-eureka-server/target/spring-eureka-server-0.0.1-SNAPSHOT.jar"]
Project spring-eureka-zuul:
FROM java:8
FROM maven:alpine
# image layer
WORKDIR /app/spring-eureka-zuul
# Image layer: with the application
COPY ./spring-eureka-zuul /app/spring-eureka-zuul
RUN mvn -v
RUN mvn clean install -DskipTests
ENTRYPOINT ["java", "-Deureka.client.serviceUrl.defaultZone=http://eureka:8761/eureka","-jar","/app/spring-eureka-zuul/target/spring-eureka-zuul-0.0.1-SNAPSHOT.jar"]
Project spring-eureka-team:
FROM java:8
FROM maven:alpine
# image layer
WORKDIR /app/spring-eureka-team
# Image layer: with the application
COPY ./spring-eureka-team /app/spring-eureka-team
RUN mvn -v
RUN mvn clean install -DskipTests
ENTRYPOINT ["java", "-Deureka.client.serviceUrl.defaultZone=http://eureka:8761/eureka","-jar","/app/spring-eureka-team/target/spring-eureka-team-0.0.1-SNAPSHOT.jar"]
Project spring-eureka-member:
FROM java:8
FROM maven:alpine
# image layer
WORKDIR /app/spring-eureka-member
# Image layer: with the application
COPY ./spring-eureka-member /app/spring-eureka-member
RUN mvn -v
RUN mvn clean install -DskipTests
ENTRYPOINT ["java", "-Deureka.client.serviceUrl.defaultZone=http://eureka:8761/eureka","-jar","/app/spring-eureka-member/target/spring-eureka-member-0.0.1-SNAPSHOT.jar"]
Bước 3:
Chạy thử. Thực hiện chạy docker compose để build image và container như sau: Mở cmd tại vị trí file docker-compose.yml gõ lệnh: docker-compose up
Kết quả được như sau:
Quá trình Docker Build Image và Container sẽ lâu, thời gian chờ sẽ phụ thuộc vào mạng và cấu hình máy.
Sau khi docker đã start hết các container chứa service của mình lên, chúng ta truy cập địa chỉ: http://localhost:8761 để kiểm tra các service đã hoạt động hay chưa. Nếu các service đã hoạt động như hình dưới, thì khi đó chương trình đã hoạt động tốt.
5. Lời kết
Vậy là chúng tôi đã giới thiệu với các bạn về Microservice, các bước triển khai một hệ thống Microservice, cũng như cách thức ứng dụng Docker với giải pháp kiến trúc này. Mong rằng các bạn đã có thêm kinh nghiệm để tự tin triển khai trong các dự án tương lai của mình.
Nguyễn Duy Quang & Nguyễn Văn Thành – CO-WELL Asia
===========================
Xem thêm các bài viết thú vị tại fanpage CO-WELL Asia