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