Docker和Spring Boot是很是流行的組合,咱們將利用GitLab CI的優點,並在應用程序服務器上自動構建,推送和運行Docker鏡像。css
Gitlab CI/CD服務是GitLab的一部分。開發人員將代碼推送到GitLab存儲庫時,GitLab CI就會在用戶指定的環境中自動構建,測試和存儲最新的代碼更改。java
選擇GitLab CI的一些主要緣由:git
易於學習,使用和可擴展spring
維護容易docker
整合容易apache
CI徹底屬於GitLab存儲庫的一部分ubuntu
良好的Docker集成bash
鏡像託管(Container registry)-基本上是你本身的私有Docker Hub服務器
從成本上來講,GitLab CI是一個很好的解決方案。每月你有2000分鐘的免費構建時間,對於某些項目來講,這是綽綽有餘的app
這無疑是一個普遍討論的話題,可是在本文中,咱們將不深刻探討該話題。GitLab CI和Jenkins都有優勢和缺點,它們都是功能很是強大的工具。
如前所述,Gitlab CI是GitLab存儲庫的一部分,這就意味着當咱們有了GitLab後,就不須要再安裝Gitlab CI,也不須要額外維護。而且只須要編寫一個.gitlab-ci.yml文件(下文會詳細說明),你便完成了全部CI工做。
對於小型項目使用Jenkins,你就必須本身配置全部內容。一般,你還須要一臺專用的Jenkins服務器,這也須要額外的成本和維護。
若是須要與這些前提條件有關的任何幫助,我已提供相應指南的連接。
你已經在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鏡像有什麼影響。
讓咱們從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 8ARG SPRING_ACTIVE_PROFILEMAINTAINER JasminCOPY 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_PROFILEFROM openjdk:11-slim#FROM openjdk:8-alpine FOR JAVA 8WORKDIR /appCOPY --from=MAVEN_BUILD /build/target/appdemo-*.jar /app/appdemo.jarENTRYPOINT ["java", "-jar", "appdemo.jar"]
讓咱們從Docker的角度看一下Java 8和11之間的區別。長話短說:這是Docker鏡像的大小和部署時間。
基於Java 8構建的Docker鏡像將明顯小於基於Java 11的鏡像。這也意味着Java 8項目的構建和部署時間將更快。
Java 8-構建時間:約4分鐘,鏡像大小爲 約180 MB
Java 11-構建時間:約14分鐘,鏡像大小約爲480 MB
注意:在實際應用中,這些數字可能會有所不一樣。
正如在前面示例中已經看到的那樣,因爲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修復。
在編寫此文件以前,要準備的東西不多。基本上,咱們想要實現的是,只要推送代碼,就會在相應的環境上自動部署。
咱們首先須要建立包含與環境相關的分支和.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存儲庫,而後轉到: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變量應以下所示。
最後,讓咱們建立將全部內容放在一塊兒的文件。
services: - docker:19.03.7-dindstages: - build jar - build and push docker image - deploybuild: 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/*.jardocker 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_REPOdeploy: 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=$SPR