เรียนรู้วิธีปรับใช้ไมโครเซอร์วิส ส่วนที่ 1 Spring Boot และ Docker

เรียนรู้วิธีปรับใช้ไมโครเซอร์วิส ส่วนที่ 1 Spring Boot และ Docker

เฮ้ ฮาเบอร์.

ในบทความนี้ ฉันต้องการพูดคุยเกี่ยวกับประสบการณ์ของฉันในการสร้างสภาพแวดล้อมการเรียนรู้สำหรับการทดลองกับไมโครเซอร์วิส เมื่อฉันได้เรียนรู้เครื่องมือใหม่แต่ละชิ้น ฉันอยากลองมาโดยตลอดไม่เพียงแต่ในเครื่องท้องถิ่นเท่านั้น แต่ยังอยากลองใช้ในสภาพที่สมจริงยิ่งขึ้นด้วย ดังนั้นฉันจึงตัดสินใจสร้างแอปพลิเคชันไมโครเซอร์วิสที่เรียบง่ายซึ่งสามารถ "ครอบคลุม" ด้วยเทคโนโลยีที่น่าสนใจทุกประเภทได้ในภายหลัง ข้อกำหนดหลักสำหรับโครงการนี้คือความใกล้ชิดกับระบบจริงสูงสุด

ในตอนแรก ฉันแบ่งการสร้างโครงการออกเป็นหลายขั้นตอน:

  1. สร้างบริการสองอย่าง - 'แบ็กเอนด์' (แบ็กเอนด์) และ 'เกตเวย์' (เกตเวย์) บรรจุลงในอิมเมจนักเทียบท่าและตั้งค่าให้ทำงานร่วมกัน

    คำสำคัญ: Java 11, Spring Boot, Docker, การเพิ่มประสิทธิภาพรูปภาพ

  2. การพัฒนาการกำหนดค่า Kubernetes และการปรับใช้ระบบใน Google Kubernetes Engine

    คำสำคัญ: Kubernetes, GKE, การจัดการทรัพยากร, การปรับขนาดอัตโนมัติ, ข้อมูลลับ

  3. การสร้างแผนภูมิด้วย Helm 3 เพื่อการจัดการคลัสเตอร์ที่ดีขึ้น

    แท็ก: Helm 3, การใช้งานแผนภูมิ

  4. การตั้งค่า Jenkins และไปป์ไลน์สำหรับการส่งโค้ดไปยังคลัสเตอร์โดยอัตโนมัติ

    คำสำคัญ: การกำหนดค่า Jenkins, ปลั๊กอิน, ที่เก็บการกำหนดค่าแยกต่างหาก

ฉันวางแผนที่จะอุทิศบทความแยกต่างหากในแต่ละขั้นตอน

จุดเน้นของบทความชุดนี้ไม่ใช่วิธีการเขียนไมโครเซอร์วิส แต่จะทำให้ไมโครเซอร์วิสทำงานในระบบเดียวได้อย่างไร แม้ว่าสิ่งเหล่านี้มักจะอยู่นอกเหนือความรับผิดชอบของนักพัฒนา แต่ฉันคิดว่ามันยังคงมีประโยชน์ที่จะทำความคุ้นเคยกับสิ่งเหล่านี้อย่างน้อย 20% (ซึ่งอย่างที่คุณทราบให้ผลลัพธ์ 80%) หัวข้อสำคัญที่ไม่มีเงื่อนไขบางหัวข้อ เช่น ความปลอดภัย จะถูกละเว้นจากโปรเจ็กต์นี้ เนื่องจากผู้เขียนมีความเข้าใจเพียงเล็กน้อยเกี่ยวกับระบบนี้ที่ถูกสร้างขึ้นเพื่อการใช้งานส่วนตัวเท่านั้น ฉันยินดีรับความคิดเห็นและคำวิจารณ์ที่สร้างสรรค์

การสร้างไมโครเซอร์วิส

บริการนี้เขียนด้วย Java 11 โดยใช้ Spring Boot การโต้ตอบระหว่างบริการถูกจัดระเบียบโดยใช้ REST โปรเจ็กต์นี้จะรวมการทดสอบจำนวนขั้นต่ำ (เพื่อที่จะได้มีบางอย่างให้ทดสอบในเจนกินส์ในภายหลัง) ซอร์สโค้ดสำหรับบริการมีอยู่ใน GitHub: แบ็กเอนด์ и ประตู.

เพื่อให้สามารถตรวจสอบสถานะของแต่ละบริการได้ จึงได้เพิ่ม Spring Actuator ในการขึ้นต่อกัน โดยจะสร้างตำแหน่งข้อมูล /actuator/health และส่งคืนสถานะ 200 หากบริการพร้อมที่จะยอมรับการรับส่งข้อมูล หรือ 504 หากมีปัญหา ในกรณีนี้ นี่เป็นการตรวจสอบที่ค่อนข้างสมมติ เนื่องจากบริการต่างๆ นั้นง่ายมาก และในกรณีที่มีเหตุสุดวิสัย บริการเหล่านี้มีแนวโน้มที่จะใช้งานไม่ได้โดยสิ้นเชิงมากกว่าที่ยังคงใช้งานได้บางส่วน แต่ในระบบจริง Actuator สามารถช่วยวินิจฉัยปัญหาก่อนที่ผู้ใช้จะเริ่มต่อสู้กับปัญหานั้น ตัวอย่างเช่น หากมีปัญหาในการเข้าถึงฐานข้อมูล เราสามารถตอบสนองต่อสิ่งนี้ได้โดยอัตโนมัติโดยการหยุดการประมวลผลคำขอที่มีอินสแตนซ์บริการที่ใช้งานไม่ได้

บริการส่วนหลัง

บริการแบ็กเอนด์จะนับและส่งคืนจำนวนคำขอที่ยอมรับ

รหัสตัวควบคุม:

@RestController
public class RequestsCounterController {

    private final AtomicLong counter = new AtomicLong();

    @GetMapping("/requests")
    public Long getRequestsCount() {
        return counter.incrementAndGet();
    }
}

การทดสอบคอนโทรลเลอร์:

@WebMvcTest(RequestsCounterController.class)
public class RequestsCounterControllerTests {

    @Autowired
    private MockMvc mockMvc;

    @Test
    public void firstRequest_one() throws Exception {
        mockMvc.perform(get("/requests"))
            .andExpect(status().isOk())
            .andExpect(MockMvcResultMatchers.content().string("1"));
    }
}

เกตเวย์บริการ

เกตเวย์จะส่งต่อคำขอไปยังบริการแบ็กเอนด์ โดยเสริมด้วยข้อมูลต่อไปนี้:

  • รหัสเกตเวย์ จำเป็นเพื่อให้สามารถแยกแยะอินสแตนซ์หนึ่งของเกตเวย์จากอีกอินสแตนซ์หนึ่งได้โดยการตอบกลับของเซิร์ฟเวอร์
  • "ความลับ" บางอย่างที่จะมีบทบาทเป็นรหัสผ่านที่สำคัญมาก (จำนวนคีย์เข้ารหัสของคุกกี้ที่สำคัญ)

การกำหนดค่าใน application.properties:

backend.url=http://localhost:8081
instance.id=${random.int}
secret="default-secret"

อะแดปเตอร์แบ็กเอนด์:

@Service
public class BackendAdapter {

    private static final String REQUESTS_ENDPOINT = "/requests";

    private final RestTemplate restTemplate;

    @Value("${backend.url}")
    private String backendUrl;

    public BackendAdapter(RestTemplateBuilder builder) {
        restTemplate = builder.build();
    }

    public String getRequests() {
        ResponseEntity<String> response = restTemplate.getForEntity(
backendUrl + REQUESTS_ENDPOINT, String.class);
        return response.getBody();
    }
}

ตัวควบคุม:

@RestController
@RequiredArgsConstructor
public class EndpointController {

    private final BackendAdapter backendAdapter;

    @Value("${instance.id}")
    private int instanceId;

    @Value("${secret}")
    private String secret;

    @GetMapping("/")
    public String getRequestsCount() {
        return String.format("Number of requests %s (gateway %d, secret %s)", backendAdapter.getRequests(), instanceId, secret);
    }
}

ปล่อย:

เราเริ่มต้นแบ็กเอนด์:

./mvnw package -DskipTests
java -Dserver.port=8081 -jar target/microservices-backend-1.0.0.jar

การเริ่มต้นเกตเวย์:

./mvnw package -DskipTests
java -jar target/microservices-gateway-1.0.0.jar

เราตรวจสอบ:

$ curl http://localhost:8080/
Number of requests 1 (gateway 38560358, secret "default-secret")

ทุกอย่างทำงานได้ ผู้อ่านที่เอาใจใส่จะทราบว่าไม่มีสิ่งใดขัดขวางไม่ให้เราเข้าถึงแบ็กเอนด์โดยตรง โดยข้ามเกตเวย์ (http://localhost:8081/requests). เพื่อแก้ไขปัญหานี้ บริการต่างๆ จะต้องรวมกันเป็นเครือข่ายเดียว และมีเพียงเกตเวย์เท่านั้นที่ควร "โดดเด่น" ภายนอก
นอกจากนี้ ทั้งสองบริการยังแชร์ระบบไฟล์เดียวกัน สร้างสตรีม และในขณะเดียวกันก็สามารถเริ่มรบกวนซึ่งกันและกันได้ คงจะดีไม่น้อยหากแยกไมโครเซอร์วิสของเราออกจากกัน ซึ่งสามารถทำได้โดยการกระจายแอปพลิเคชันบนเครื่องต่างๆ (ใช้เงินจำนวนมาก ยาก) โดยใช้เครื่องเสมือน (ใช้ทรัพยากรจำนวนมาก เริ่มต้นระบบเป็นเวลานาน) หรือการใช้คอนเทนเนอร์ ตามที่คาดไว้เราเลือกตัวเลือกที่สามและ นักเทียบท่า เป็นเครื่องมือในการบรรจุ

นักเทียบท่า

กล่าวโดยสรุป นักเทียบท่าจะสร้างคอนเทนเนอร์ที่แยกได้ หนึ่งคอนเทนเนอร์ต่อแอปพลิเคชัน หากต้องการใช้นักเทียบท่า คุณต้องเขียน Dockerfile - คำแนะนำในการสร้างและเรียกใช้แอปพลิเคชัน ถัดไป คุณสามารถสร้างอิมเมจ อัปโหลดไปยังรีจีสทรีอิมเมจได้ (หมายเลข XNUMX) Dockerhub) และปรับใช้ไมโครเซอร์วิสของคุณในสภาพแวดล้อมที่มีการเชื่อมต่อด้วยคำสั่งเดียว

ไฟล์นักเทียบท่า

ลักษณะที่สำคัญที่สุดประการหนึ่งของภาพคือขนาดของภาพ รูปภาพขนาดกะทัดรัดจะดาวน์โหลดเร็วขึ้นจากพื้นที่เก็บข้อมูลระยะไกล ใช้พื้นที่น้อยลง และบริการของคุณจะเริ่มเร็วขึ้น รูปภาพใด ๆ ถูกสร้างขึ้นบนพื้นฐานของรูปภาพพื้นฐานและขอแนะนำให้เลือกตัวเลือกที่เรียบง่ายที่สุด ตัวเลือกที่ดีคือ Alpine ซึ่งเป็นระบบปฏิบัติการ Linux ที่สมบูรณ์พร้อมแพ็คเกจขั้นต่ำ

ก่อนอื่น เรามาลองเขียน Dockerfile "บนหน้าผาก" กันดีกว่า (ฉันจะบอกทันทีว่านี่เป็นวิธีที่ไม่ดี อย่าทำ):

FROM adoptopenjdk/openjdk11:jdk-11.0.5_10-alpine
ADD . /src
WORKDIR /src
RUN ./mvnw package -DskipTests
EXPOSE 8080
ENTRYPOINT ["java","-jar","target/microservices-gateway-1.0.0.jar"]

ที่นี่เรากำลังใช้อิมเมจพื้นฐานแบบ Alpine โดยติดตั้ง JDK ไว้แล้วเพื่อสร้างโปรเจ็กต์ของเรา ด้วยคำสั่ง ADD เราจะเพิ่มไดเร็กทอรี src ปัจจุบันให้กับอิมเมจ ทำเครื่องหมายว่าใช้งานได้ (WORKDIR) และเริ่มสร้าง คำสั่ง EXPOSE 8080 จะส่งสัญญาณไปยังนักเทียบท่าว่าแอปพลิเคชันในคอนเทนเนอร์จะใช้พอร์ต 8080 (ซึ่งจะไม่ทำให้แอปพลิเคชันสามารถเข้าถึงได้จากภายนอก แต่จะทำให้แอปพลิเคชันสามารถเข้าถึงได้ เช่น จากคอนเทนเนอร์อื่นบนเครือข่ายนักเทียบท่าเดียวกัน ).

หากต้องการจัดแพคเกจบริการเป็นอิมเมจ คุณต้องรันคำสั่งจากรากของแต่ละโปรเจ็กต์:

docker image build . -t msvc-backend:1.0.0

ผลลัพธ์คืออิมเมจ 456 MB (ซึ่งอิมเมจ JDK พื้นฐานครอบครอง 340 MB) และถึงแม้ว่าคลาสในโครงการของเราสามารถนับได้ด้วยนิ้วเดียวก็ตาม วิธีลดขนาดรูปภาพของเรา:

  • เราใช้การประกอบแบบหลายขั้นตอน ในขั้นตอนแรก เราจะสร้างโปรเจ็กต์ ในขั้นตอนที่สอง เราจะติดตั้ง JRE และในขั้นตอนที่สาม เราจะคัดลอกทั้งหมดลงในอิมเมจ Alpine ใหม่ที่ดูสะอาดตา โดยรวมแล้วมีเพียงส่วนประกอบที่จำเป็นเท่านั้นที่จะอยู่ในรูปภาพสุดท้าย
  • ลองใช้การทำให้เป็นโมดูลของจาวา เริ่มต้นด้วย Java 9 คุณสามารถใช้เครื่องมือ jlink เพื่อสร้าง JRE จากโมดูลที่คุณต้องการ

สำหรับผู้ที่อยากรู้อยากเห็น ต่อไปนี้เป็นบทความดีๆ เกี่ยวกับแนวทางการลดขนาดภาพ https://habr.com/ru/company/ruvds/blog/485650/.

Dockerfile สุดท้าย:

FROM adoptopenjdk/openjdk11:jdk-11.0.5_10-alpine as builder
ADD . /src
WORKDIR /src
RUN ./mvnw package -DskipTests

FROM alpine:3.10.3 as packager
RUN apk --no-cache add openjdk11-jdk openjdk11-jmods
ENV JAVA_MINIMAL="/opt/java-minimal"
RUN /usr/lib/jvm/java-11-openjdk/bin/jlink 
    --verbose 
    --add-modules 
        java.base,java.sql,java.naming,java.desktop,java.management,java.security.jgss,java.instrument 
    --compress 2 --strip-debug --no-header-files --no-man-pages 
    --release-info="add:IMPLEMENTOR=radistao:IMPLEMENTOR_VERSION=radistao_JRE" 
    --output "$JAVA_MINIMAL"

FROM alpine:3.10.3
LABEL maintainer="Anton Shelenkov [email protected]"
ENV JAVA_HOME=/opt/java-minimal
ENV PATH="$PATH:$JAVA_HOME/bin"
COPY --from=packager "$JAVA_HOME" "$JAVA_HOME"
COPY --from=builder /src/target/microservices-backend-*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/app.jar"]

เราสร้างภาพขึ้นใหม่ และด้วยเหตุนี้ ภาพจึงสูญเสียน้ำหนักไป 6 เท่า หรือคิดเป็น 77 MB ไม่เลว. หลังจากนั้นสามารถอัปโหลดรูปภาพสำเร็จรูปไปยังรีจิสตรีรูปภาพเพื่อให้รูปภาพของคุณพร้อมให้ดาวน์โหลดจากอินเทอร์เน็ต

บริการร่วมใน Docker

ประการแรก บริการของเราจะต้องอยู่ในเครือข่ายเดียวกัน มีเครือข่ายหลายประเภทในนักเทียบท่าและเราใช้เครือข่ายดั้งเดิมที่สุด - บริดจ์ซึ่งช่วยให้คุณสร้างเครือข่ายคอนเทนเนอร์ที่ทำงานบนโฮสต์เดียวกัน สร้างเครือข่ายด้วยคำสั่งต่อไปนี้:

docker network create msvc-network

ถัดไป ให้เริ่มคอนเทนเนอร์แบ็กเอนด์ชื่อ 'แบ็กเอนด์' ด้วยอิมเมจ microservices-backend:1.0.0:

docker run -dit --name backend --network msvc-net microservices-backend:1.0.0

เป็นที่น่าสังเกตว่าเครือข่ายบริดจ์ให้บริการแบบสำเร็จรูปสำหรับคอนเทนเนอร์ตามชื่อของพวกเขา นั่นคือบริการแบ็กเอนด์จะพร้อมใช้งานภายในเครือข่ายนักเทียบท่าที่ http://backend:8080.

การเริ่มต้นเกตเวย์:

docker run -dit -p 80:8080 --env secret=my-real-secret --env BACKEND_URL=http://backend:8080/ --name gateway --network msvc-net microservices-gateway:1.0.0

ในคำสั่งนี้ เราระบุว่าเรากำลังส่งต่อพอร์ต 80 ของโฮสต์ของเราไปยังพอร์ต 8080 ของคอนเทนเนอร์ เราใช้ตัวเลือก env เพื่อตั้งค่าตัวแปรสภาพแวดล้อมที่สปริงจะอ่านโดยอัตโนมัติและแทนที่คุณสมบัติจาก application.properties

หลังจากเริ่มต้นเราก็โทร http://localhost/ และตรวจสอบให้แน่ใจว่าทุกอย่างทำงานได้เหมือนในกรณีก่อนหน้า

ข้อสรุป

ด้วยเหตุนี้ เราจึงสร้างไมโครเซอร์วิสง่ายๆ สองรายการ บรรจุไว้ในคอนเทนเนอร์นักเทียบท่า และเปิดใช้งานพร้อมกันบนเครื่องเดียวกัน อย่างไรก็ตาม ระบบผลลัพธ์มีข้อเสียหลายประการ:

  • ความทนทานต่อข้อผิดพลาดไม่ดี - ทุกอย่างใช้ได้กับเราบนเซิร์ฟเวอร์เดียว
  • ความสามารถในการปรับขนาดไม่ดี - เมื่อโหลดเพิ่มขึ้น คงจะดีถ้าปรับใช้อินสแตนซ์บริการเพิ่มเติมโดยอัตโนมัติ และปรับสมดุลโหลดระหว่างอินสแตนซ์เหล่านั้น
  • ความซับซ้อนของการเปิดตัว - เราจำเป็นต้องป้อนคำสั่งอย่างน้อย 3 คำและมีพารามิเตอร์บางอย่าง (สำหรับ 2 บริการเท่านั้น)

เพื่อแก้ไขปัญหาข้างต้น มีวิธีแก้ไขปัญหามากมาย เช่น Docker Swarm, Nomad, Kubernetes หรือ OpenShift หากทั้งระบบเขียนด้วย Java คุณสามารถมองไปที่ Spring Cloud (บทความดีๆ).

В ส่วนถัดไป ฉันจะพูดถึงวิธีตั้งค่า Kubernetes และปรับใช้โปรเจ็กต์กับ Google Kubernetes Engine

ที่มา: will.com

เพิ่มความคิดเห็น