學習部署微服務。 第 1 部分:Spring Boot 和 Docker

學習部署微服務。 第 1 部分:Spring Boot 和 Docker

嘿哈布爾。

在本文中,我想談談我創建微服務實驗學習環境的經驗。 當學習每一個新工具時,我總是想不僅在本地機器上嘗試,而且在更現實的條件下嘗試。 因此,我決定創建一個簡化的微服務應用程序,稍後可以將其「掛」上各種有趣的技術。 該專案的主要要求是其功能與真實系統的最大程度的接近。

最初,我將專案的建立分為幾個步驟:

  1. 建立兩個服務 - 'backend' 和 'gateway',將它們打包到 docker 映像中並將它們配置為協同工作

    關鍵字:Java 11、Spring Boot、Docker、映像優化

  2. Google Kubernetes Engine 中 Kubernetes 設定與部署系統的開發

    關鍵字:Kubernetes、GKE、資源管理、自動擴充、秘密

  3. 使用 Helm 3 建立圖表以實現更有效率的叢集管理

    關鍵字:Helm 3、圖表部署

  4. 設定 Jenkins 和管道以自動將程式碼交付到集群

    關鍵字:Jenkins 配置、插件、單獨的配置儲存庫

我計劃為每個步驟專門寫一篇文章。

本系列文章的重點不是如何寫微服務,而是如何使它們在單一系統中運作。 雖然所有這些事情通常都不在開發人員的職責範圍內,但我認為至少 20% 的人熟悉它們仍然很有用(眾所周知,這佔結果的 80%)。 一些絕對重要的主題,例如安全性,將被排除在這個專案之外,因為作者對此知之甚少;該系統是專為個人使用而創建的。 我歡迎任何意見和建設性批評。

創建微服務

這些服務是使用 Spring Boot 用 Ja​​va 11 編寫的。 服務間通訊是使用 REST 組織的。 該項目將包含最少數量的測試(以便稍後在 Jenkins 中進行測試)。 該服務的源代碼可在 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"));
    }
}

網關服務

網關會將請求轉送到後端服務,並補充以下資訊:

  • 網關 ID。 需要它,以便可以透過伺服器回應區分網關的一個實例與另一個實例
  • 某個「秘密」將充當非常重要的密碼(用於加密重要 cookie 的密鑰號碼)

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)。 為了解決這個問題,必須將服務合併到一個網路中,並且只有網關應該「伸出」到外面。
此外,這兩個服務共享相同的檔案系統,產生線程,並且在某個時刻可能開始相互幹擾。 隔離我們的微服務會很好。 這可以透過將應用程式分佈在不同的機器上(需要大量資金、困難)、使用虛擬機器(資源密集、啟動時間長)或使用容器化來實現。 如所料,我們選擇第三個選項 碼頭工人 作為容器化的工具。

碼頭工人

簡而言之,Docker 創建隔離的容器,每個應用程式一個。 要使用 Docker,您需要編寫 Dockerfile - 用於建置和執行應用程式的說明。 接下來,您可以建立鏡像,並將其上傳到鏡像註冊表(No.XNUMX)。 碼頭工人)並透過一個命令在任何 Docker 化環境中部署您的微服務。

Dockerfile

圖像最重要的特徵之一是它的大小。 緊湊的映像將從遠端儲存庫下載得更快,佔用的空間更少,並且您的服務啟動得更快。 任何圖像都是建立在基本圖像的基礎上的,建議選擇最簡約的選項。 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 命令向 docker 發出信號,表明容器中的應用程式將使用其連接埠 8080(這不會使應用程式從外部訪問,但允許從同一 docker 網路上的另一個容器存取該應用程式) )。

要將服務打包成鏡像,需要在每個專案的根目錄中執行以下命令:

docker image build . -t msvc-backend:1.0.0

結果,我們得到了一個大小為 456 MB 的映像(其中基本 JDK 340 映像佔據了 MB)。 儘管我們專案中的課程用一根手指頭就能數出來。 要減小圖像的大小:

  • 我們使用多步驟組裝。 第一步我們將組裝項目,第二步我們將安裝 JRE,第三步我們將把所有這些複製到一個新的乾淨的 Alpine 映像中。 總的來說,最終圖像將僅包含必要的組件。
  • 讓我們使用java模組化。 從 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。 不錯。 然後,完成的圖像可以上傳到圖像註冊表,以便可以從 Internet 下載圖像。

在 Docker 中一起運行服務

首先,我們的服務必須位於同一網路上。 Docker中有多種類型的網絡,我們使用其中最原始的網絡——橋接器,它允許您將運行在同一主機上的容器聯網。 讓我們使用以下命令建立一個網路:

docker network create msvc-network

接下來,我們啟動一個名為「backend」的後端容器,其鏡像為 microservices-backend:1.0.0:

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

值得注意的是,橋接網路透過容器的名稱為容器提供開箱即用的服務發現。 也就是說,後端服務將在 Docker 網路中可用 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 選項來設定 spring 自動讀取的環境變數並覆寫 application.properties 中的屬性。

啟動後,調用 http://localhost/ 並確保一切正常,就像前面的情況一樣。

結論

因此,我們創建了兩個簡單的微服務,將它們打包在 docker 容器中,並在同一台機器上一起啟動。 然而,最終的系統有許多缺點:

  • 容錯能力差-我們的一切都在一台伺服器上運行
  • 可擴展性差——隨著負載的增加,自動部署額外的服務實例並平衡它們之間的負載會很好
  • 啟動複雜度 - 我們需要輸入至少 3 個命令,並帶有某些參數(這只適用於 2 個服務)

為了解決上述問題,有多種解決方案,例如 Docker Swarm、Nomad、Kubernetes 或 OpenShift。 如果整個系統都是用Java寫的,可以看看Spring Cloud(好文章).

В 下一部分 我將告訴您如何設定 Kubernetes 並將專案部署到 Google Kubernetes Engine。

來源: www.habr.com

添加評論