原文發表於kubernetes中文社區,爲作者原創翻譯 ,原文地址
更多kubernetes文章,請多關注kubernetes中文社區
Docker和Spring Boot是非常流行的組合,我們將利用GitLab CI的優勢,並在應用程序服務器上自動構建,推送和運行Docker鏡像。
GitLab CI
Gitlab CI/CD服務是GitLab的一部分,每當開發人員將代碼推送到GitLab存儲庫時,它都會在所需的環境中構建,測試和存儲最新的更改。
選擇GitLab CI的一些主要原因:
-
易於學習,使用和可擴展
-
維護容易
-
整合容易
-
CI完全屬於GitLab存儲庫的一部分
-
良好的Docker集成
-
鏡像託管(Container registry)-基本上是你自己的私有Docker Hub
-
從成本上來說,GitLab CI是一個很好的解決方案。每個月你有2000分鐘的免費構建時間,對於某些項目來說,這是綽綽有餘的
爲什麼GitLab CI超越Jenkins
這無疑是一個廣泛討論的話題,但是在本文中,我們將不深入探討該話題。GitLab CI和Jenkins都有優點和缺點,它們都是功能非常強大的工具。
那爲什麼選擇GitLab?
如前所述,CI完全是GitLab存儲庫的一部分,這意味着不需要安裝它,並且維護最少。yml腳本完成後,你便或多或少地完成了所有工作。
對於小型項目使用Jenkins,你就必須自己設置和配置所有內容。通常,你還需要一臺專用的Jenkins服務器,這也需要額外的成本和維護。
使用GitLab CI 前提條件
如果需要與這些前提條件有關的任何幫助,我已提供相應指南的鏈接。
-
你已經在GitLab上推送了Spring Boot項目
-
你已在應用程序服務器上安裝了Docker(指南)
-
你具有Docker鏡像的鏡像託管(在本指南中將使用Docker Hub)
-
你已經在服務器上生成了SSH RSA密鑰(指南)
你要創建什麼
你將創建Dockerfile 和.gitlab-ci.yml, 它們將自動用於:
-
構建應用程序Jar文件
-
構建Docker鏡像
-
將鏡像推送到Docker存儲庫
-
在應用程序服務器上運行鏡像
基本項目信息
本文的Spring Boot應用程序是通過Spring Initializr生成的。這是一個基於Java 8或Java11構建的Maven項目。後面,我們將介紹Java 8和Java 11對Docker鏡像有什麼影響。
Docker文件
讓我們從Dockerfile開始。
FROM maven:3.6.3-jdk-11-slim AS MAVEN_BUILD
#FROM maven:3.5.2-jdk-8-alpine AS MAVEN_BUILD FOR JAVA 8
ARG SPRING_ACTIVE_PROFILE
MAINTAINER Jasmin
COPY pom.xml /build/
COPY src /build/src/
WORKDIR /build/
RUN mvn clean install -Dspring.profiles.active=$SPRING_ACTIVE_PROFILE && mvn package -B -e -Dspring.profiles.active=$SPRING_ACTIVE_PROFILE
FROM openjdk:11-slim
#FROM openjdk:8-alpine FOR JAVA 8
WORKDIR /app
COPY --from=MAVEN_BUILD /build/target/appdemo-*.jar /app/appdemo.jar
ENTRYPOINT ["java", "-jar", "appdemo.jar"]
與該Dockerfile相關的知識很少。
Java版本
讓我們從Docker的角度看一下Java 8和11之間的區別。長話短說:這是Docker鏡像的大小和部署時間。
基於Java 8構建的Docker鏡像將明顯小於基於Java 11的鏡像。這也意味着Java 8項目的構建和部署時間將更快。
-
Java 8-構建時間:約4分鐘,鏡像大小爲 約180 MB
-
Java 11-構建時間: 約14分鐘,鏡像大小約爲480 MB
注意: 在實際應用中,這些數字可能會有所不同。
Docker鏡像
正如在前面示例中已經看到的那樣,由於Java版本的緣故,我們在應用程序鏡像大小和構建時間方面存在巨大差異。其背後的實際原因是在Dockerfile中使用了Docker鏡像。
如果我們再看一下Dockerfile,那麼Java 11鏡像很大的真正原因是因爲它包含了沒有經過驗證/測試的open-jdk:11鏡像的Alpine版本。
如果你不熟悉OpenJDK鏡像版本,建議你閱讀OpenJDK Docker官方文檔。在這裏,你可以找到有關每個OpenJDK版本的鏡像的說明。
備註:動態的變量
在ENTRYPOINT
中,與環境相關的屬性,我們只能寫死,如下:
ENTRYPOINT [ “ java”,“ -Dspring.profiles.active = development”,“ -jar”,“ appdemo.jar” ]
爲了使它動態,你希望將其簡單地轉換爲:
ENTRYPOINT [ “ java”,“ -Dspring.profiles.active = $ SPRINT_ACTIVE_PROFILE”,“ -jar”,“ appdemo.jar” ]
以前,這是不可能的,但是幸運的是,這將在.gitlab-ci.yml中通過 ARG SPRING_ACTIVE_PROFILE修復。
gitlab-ci.yml
在編寫此文件之前,要準備的東西很少。基本上,我們想要實現的是,只要推送代碼,就會在相應的環境上自動部署。
創建.env文件和分支
我們首先需要創建包含與環境相關的分支和.env文件。每個分支實際上代表我們的應用程序將運行的環境。
我們將在三個不同的環境中部署我們的應用程序:開發,測試和生產( development, QA, and production )。這意味着我們需要創建三個分支。
我們的dev,QA和prod應用程序將在不同的服務器上運行,並且將具有不同的Docker容器標籤,端口和SSH密鑰。這就要求我們的gitlab-ci.yml文件將要是動態的,通過爲我們擁有的每個環境創建.env文件來解決該問題。
.develop.env .qa.env .master.env
重要說明: 命名這些文件時,有一個簡單的規則:使用GitLab分支來命名,因此文件名應如下所示:。$ BRANCH_NAME.env
例如,這是.develop.env文件。
export SPRING_ACTIVE_PROFILE='development'
export DOCKER_REPO='username/demo_app:dev'
export APP_NAME='demo_app_dev'
export PORT='8080'
export SERVER_IP='000.11.222.33'
export SERVER_SSH_KEY="$DEV_SSH_PRIVATE_KEY"
與.env文件有關的重要說明:
SPRING_ACTIVE_PROFILE:不言自明,我們要使用哪些Spring應用程序屬性。 DOCKER_REPO:這是Docker鏡像的存儲庫;在這裏,我們唯一需要注意的是Docker image TAG,對於每種環境,我們將使用不同的標籤,這意味着我們將使用dev,qa 和prod 標籤。
我們的Docker中心看起來像這樣。
如你所見,存在一個帶有三個不同標籤的存儲庫,每當將代碼推送到GitLab分支上時,每個標籤(應用程序版本)都會被更新。
-
APP_NAME: 此屬性非常重要,它是對容器的命名。 如果你未設置此屬性,則Docker將爲你的容器隨機命名。這可能是一個問題,因爲你將無法以乾淨的方式停止運行容器。
-
端口:這是我們希望運行Docker容器的端口。
-
SERVER_IP:應用程序使用的服務器IP。通常,每個環境都將位於不同的服務器上。
-
SERVER_SSH_KEY:這是我們已經在每臺服務器上生成的SSH密鑰。
$DEV_SSH_PRIVATE_KEY
實際上是來自GitLab存儲庫的變量。
創建GitLab變量
最後需要做的是創建GitLab變量。
打開你的GitLab存儲庫,然後轉到:Settings -> CI/CD。在 Variables部分中, 添加新變量:
-
DOCKER_USER:用於訪問Docker Hub或其他鏡像託管的用戶名
-
DOCKER_PASSWORD: 用於訪問鏡像託管的密碼
-
$ ENV_SSH_PRIVATE_KEY: 先前在服務器上生成的SSH私鑰。
SSH KEY的重要說明:
-
你需要複製完整的密鑰值,包括: ----- BEGIN RSA PRIVATE KEY -----和----- END RSA PRIVATE KEY -----
最後,你的GitLab變量應如下所示。
創建gitlab-ci.yml文件
最後,讓我們創建將所有內容放在一起的文件。
services:
- docker:19.03.7-dind
stages:
- build jar
- build and push docker image
- deploy
build:
image: maven:3.6.3-jdk-11-slim
stage: build jar
before_script:
- source .${CI_COMMIT_REF_NAME}.env
script:
- mvn clean install -Dspring.profiles.active=$SPRING_ACTIVE_PROFILE && mvn package -B -e -Dspring.profiles.active=$SPRING_ACTIVE_PROFILE
artifacts:
paths:
- target/*.jar
docker build:
image: docker:stable
stage: build and push docker image
before_script:
- source .${CI_COMMIT_REF_NAME}.env
script:
- docker build --build-arg SPRING_ACTIVE_PROFILE=$SPRING_ACTIVE_PROFILE -t $DOCKER_REPO .
- docker login -u $DOCKER_USER -p $DOCKER_PASSWORD docker.io
- docker push $DOCKER_REPO
deploy:
image: ubuntu:latest
stage: deploy
before_script:
- 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config
- source .${CI_COMMIT_REF_NAME}.env
script:
- ssh root@$SERVER "docker login -u $DOCKER_USER -p $DOCKER_PASSWORD docker.io; docker stop $APP_NAME; docker system prune -a -f; docker pull $DOCKER_REPO; docker container run -d --name $APP_NAME -p $PORT:8080 -e SPRING_PROFILES_ACTIVE=$SPRING_ACTIVE_PROFILE $DOCKER_REPO; docker logout"
讓我們解釋一下這裏發生了什麼:
services:
- docker:19.03.7-dind
這是一項服務,使我們可以在Docker中使用Docker。在Docker中運行Docker通常不是一個好主意,但是對於此用例來說,這是完全可以的,因爲我們將構建鏡像並將其推送到存儲庫中。
stages:
- build jar
- build and push docker image
- deploy
對於每個gitlab-ci.yml文件,必須首先定義執行步驟。腳本將按照步驟定義的順序執行。
在每個步驟,我們都必須添加以下部分: before_script: - source .${CI_COMMIT_REF_NAME}.env
這只是預先加載之前創建的 env. files, 文件。根據正在運行的分支來自動注入變量。(這就是爲什麼我們必須使用分支名稱來命名.env文件的原因)
這些是我們部署過程中的執行步驟。
如你所見,,有三個帶有綠色複選標記的圓圈,這表示所有步驟均已成功執行。
build:
image: maven:3.6.3-jdk-11-slim
stage: build jar
before_script:
- source .${CI_COMMIT_REF_NAME}.env
script:
- mvn clean install -Dspring.profiles.active=$SPRING_ACTIVE_PROFILE && mvn package -B -e -Dspring.profiles.active=$SPRING_ACTIVE_PROFILE
artifacts:
paths:
- target/*.jar
這是執行第一步驟代碼的一部分,構建了一個jar文件,該文件可以下載。這實際上是一個可選步驟,僅用於演示構建jar並從GitLab下載它是多麼容易。
第二步驟是在Docker存儲庫中構建並推送Docker鏡像。
docker build:
image: docker:stable
stage: build and push docker image
before_script:
- source .${CI_COMMIT_REF_NAME}.env
script:
- docker build --build-arg SPRING_ACTIVE_PROFILE=$SPRING_ACTIVE_PROFILE -t $DOCKER_REPO .
- docker login -u $DOCKER_USER -p $DOCKER_PASSWORD docker.io
- docker push $DOCKER_REPO
這一步驟,我們不得不使用docker:19.03.7-dind服務。如你所見,我們使用的是最新的穩定版本的Docker,我們只是在爲適當的環境構建鏡像,然後對Dockerhub進行身份驗證並推送鏡像。
我們腳本的最後一部分是:
deploy:
image: ubuntu:latest
stage: deploy
before_script:
- 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config
- source .${CI_COMMIT_REF_NAME}.env
script:
- ssh root@$SERVER "docker stop $APP_NAME; docker system prune -a -f; docker pull $DOCKER_REPO; docker container run -d --name $APP_NAME -p $PORT:8080 -e SPRING_PROFILES_ACTIVE=$SPRING_ACTIVE_PROFILE $DOCKER_REPO"
在此步驟中,我們使用Ubuntu Docker鏡像,因此我們可以SSH到我們的應用程序服務器並運行一些Docker命令。其中的部分代碼 before_script
大部分來自官方文檔,但是,當然,我們可以對其進行一些調整以滿足我們的需求。爲不對私鑰進行驗證,添加了以下代碼行:
- echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config
你也可以參考指南驗證私鑰。 如你在最後階段的腳本部分中所見,我們正在執行一些Docker命令。
-
停止正在運行的Docker容器:
docker stop $APP_NAME
。(這就是我們要在.env文件中定義APP_NAME
的原因 ) -
刪除所有未運行的Docker鏡像
docker system prune -a -f
。這實際上不是強制性的,但我想刪除服務器上所有未使用的鏡像。 -
拉取最新版本的Docker鏡像(該鏡像是在上一個階段中構建並推送的)。
-
最後,使用以下命令運行Docker鏡像:
docker container run -d --name $APP_NAME -p $PORT:8080 -e SPRING_PROFILES_ACTIVE=$SPRING_ACTIVE_PROFILE $DOCKER_REPO
譯文鏈接: https://dzone.com/articles/automate-spring-boot-app-deployment-with-gitlab-ci