《Spring Boot 2.0極簡教程》(陳光劍)
—— 基於 Gradle + Kotlin的企業級應用開發最佳實踐
前面的章節中,咱們都是在IDE環境中開發運行測試 Spring Boot 應用程序。在開發測試發佈整個軟件生命週期的過程當中,咱們一般須要完成打包部署發佈到平常、預發、線上機器運行等運維相關工做。
本章前半部分介紹 Spring Boot 應用的打包和部署,後半部分重點介紹如何使用 Docker 來構建部署運行 Spring Boot 應用。html
首先,使用http://start.spring.io/ 建立一個打包方式爲 war 的 Spring Boot Kotlin 應用,採用 Gradle 構建。點擊 Generate Project 等待建立完畢,下載 zip 包,導入 IDEA 中。能夠看到,相比於項目打成jar 包方式,打成 war 包的項目中多了一個用於初始化Servlet的ServletInitializer類。代碼以下java
class ServletInitializer : SpringBootServletInitializer() { override fun configure(application: SpringApplicationBuilder) : SpringApplicationBuilder { return application.sources(DemoPackageAndDeployApplication::class.java) } }
咱們知道Spring Boot 默認集成了內嵌web容器(例如 Tomcat、Jetty 等),這個時候,Spring Boot 應用支持「一鍵啓動」,像一個普通Java程序同樣,從main函數入口開始啓動。如今,咱們是將項目打包成war包,放到獨立的web容器中。
而若是咱們這個 war 包中沒有配置Spring MVC 的 DispatcherServlet 的 web.xml 文件或者初始化 Servlet的類,那麼這個 war 包就不會被 Tomcat識別啓動 。這個時候,咱們須要告訴 Tomcat 這個 war 包的啓動入口。而SpringBootServletInitializer就是來完成這件事情的。
經過重寫configure (SpringApplicationBuilder) 方法,使用SpringApplicationBuilder 來配置應用程序的sources類。爲了測試應用運行的效果,咱們在DemoPackageAndDeployApplication.kt 中添加HelloWorld REST接口方便測試linux
@SpringBootApplication open class DemoPackageAndDeployApplication fun main(args: Array<String>) { runApplication<DemoPackageAndDeployApplication>(*args) } @RestController class HelloWorld { @GetMapping(value = ["", "/"]) fun hello(): Map<String, Any> { val result = mutableMapOf<String, Any>() result["msg"] = "Hello,World" result["time"] = Date() return result } }
在 IDEA 的右邊的 Gradle 工具欄中列出了 Gradle 構建項目的命令,以下圖git
圖16-1 Gradle 構建項目的命令
咱們能夠直接點擊 bootJar 把項目打成 jar 包。固然,在運維部署腳本中一般使用命令行: gradle bootJar 。執行日誌以下github
17:44:21: Executing task 'bootJar'... :compileKotlin UP-TO-DATE :compileJava NO-SOURCE :processResources UP-TO-DATE :classes UP-TO-DATE :bootJar UP-TO-DATE BUILD SUCCESSFUL in 1s 3 actionable tasks: 3 up-to-date 17:44:22: Task execution finished 'bootJar'.
執行完畢,咱們能夠在項目的build/libs 目錄下看到打好的 jar 包,以下圖所示web
圖16-2 項目的build/libs 目錄下打好的 jar 包
而後,咱們就能夠直接使用 java –jar 命令執行該 jar 包了
$ java -jar build/libs/demo_package_and_deploy-0.0.1-SNAPSHOT.jar
此時,咱們瀏覽器訪問 http://127.0.0.1:8080/ , 能夠看到輸出spring
{ "msg": "Hello,World", "time": "2018-02-09T09:38:31.933+0000" }
不過,使用java –jar 命令行來啓動系統的這種方式
java -jar build/libs/demo_package_and_deploy-0.0.1-SNAPSHOT.jar
只要控制檯關閉,服務就不能訪問了。咱們可使用nohup 與 & 命令讓進程在後臺運行:
nohup java -jar build/libs/demo_package_and_deploy-0.0.1-SNAPSHOT.jar &docker
咱們也能夠在啓動的時候選擇讀取不一樣的配置文件。例如,在項目src/main/resources 目錄下面有不一樣環境下的配置文件。以下圖所示:shell
圖16-3 不一樣環境的屬性配置文件
其中,application-dev.properties中配置服務器端口號爲9000:數據庫
server.port=9000
執行 bootJar從新打jar 包,執行下面的命令:
java -jar build/libs/demo_package_and_deploy-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev
能夠看到應用成功啓動,並監聽9000端口:
… o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 9000 (http) with context path '' 2018-02-09 18:18:47.336 INFO 69156 --- [ main] .e.s.d.DemoPackageAndDeployApplicationKt : Started DemoPackageAndDeployApplicationKt in 6.493 seconds (JVM running for 7.589)
在上面建立的項目中,Gradle 構建配置文件 build.gradle 內容以下:
buildscript { … } … apply plugin: 'war' … configurations { providedRuntime } dependencies { … providedRuntime('org.springframework.boot:spring-boot-starter-tomcat') }
其中,apply plugin: 'war' 是使用 war 插件來完成項目的打包工做。
直接使用 gradle bootWar,便可把項目打成 war包。而後,就能夠像普通J2EE項目同樣部署到web容器。一樣的,war 包的路徑默認也是放在 build/libs 下面。
另外,若是下面這行代碼還在:
@SpringBootApplication open class DemoPackageAndDeployApplication fun main(args: Array<String>) { runApplication<DemoPackageAndDeployApplication>(*args) }
項目打成的war包,依然支持java –jar 運行:
$ java -jar build/libs/demo_package_and_deploy-0.0.1-SNAPSHOT.war
這個 war 包很不錯,既能夠直接扔到 Tomcat 容器中執行,也能夠直接命令行啓動運行。
提示:項目打 war包的示例項目源代碼:https://github.com/EasySpring...
本節簡單介紹一些 Spring Boot 應用的生產運維的一些內容。
使用命令:
ps -ef|grep java
拿到對於Java程序的pid (第2列):
501 69156 68678 0 6:18PM ttys002 0:21.59 /usr/bin/java -jar build/libs/demo_package_and_deploy-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev
能夠根據java自帶的jinfo命令:
jinfo -flags 69156
來查看jar 啓動後使用的是什麼gc、新生代、老年代,分批的內存都是多少,示例以下:
$ jinfo -flags 69156 Attaching to process ID 69156, please wait... Debugger attached successfully. Server compiler detected. JVM version is 25.40-b25 Non-default VM flags: -XX:CICompilerCount=3 -XX:InitialHeapSize=134217728 -XX:MaxHeapSize=2147483648 -XX:MaxNewSize=715653120 -XX:MinHeapDeltaBytes=524288 -XX:NewSize=44564480 -XX:OldSize=89653248 -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseFastUnorderedTimeStamps -XX:+UseParallelGC
其中的參數簡單說明如表16-1所示。
表16-1 JVM參數
參數說明
-XX:CICompilerCount
最大的並行編譯數
-XX:InitialHeapSize 和 -XX:MaxHeapSize
指定JVM的初始堆內存和最大堆內存大小
-XX:MaxNewSize
JVM堆區域新生代內存的最大可分配大小
-XX:+UseParallelGC
垃圾回收使用Parallel收集器
咱們能夠在 Java 命令行中配置咱們須要的JVM參數指標。
提示:更多關於 JVM 選項參數配置參考:http://www.oracle.com/technet... 。
要想重啓應用,要首先找到該應用的 java進程,而後kill掉 java 進程。完成這個邏輯的shell 腳本以下:
kill -9 $(ps -ef|grep java|awk '{print $2}')
而後,再使用命令行從新啓動應用便可。
本節介紹如何使用 Docker 來構建部署 Spring Boot 應用。
Docker 是一個Go語言開發的開源的輕量級應用容器引擎,誕生與2013年。Docker的核心概念是:鏡像、容器、倉庫。關鍵字是: 分佈式應用(distributed applications), 微服務( microservices), 容器( containers ), 虛擬化(docker virtualization)。
Docker容器「輕量級」的含義主要是跟傳統的虛擬機方式的對比而言。以下圖所示:
圖16-4 Docker 「輕量級」容器VS.傳統的虛擬機方式
傳統的虛擬機技術是在硬件層面實現虛擬化,須要額外的虛擬機管理軟件跟虛擬機操做系統這層。而 Docker 是在操做系統層面上的虛擬化,直接使用的是本地操做系統資源,所以更加輕量級。
Docker 的主要目標是經過對應用組件的封裝、分發、部署、運行等生命週期的管理,作到「一次封裝,處處運行」。
Docker 是實現微服務( microservices )應用程序開發的理想選擇。開發、部署和回滾都將變成「一鍵操做」。傳統的在服務器上進行各類軟件包的安裝、環境配置、應用程序的打包部署、啓動進程等零散的運維操做——被更高層次的「抽象」,放到了一個「集裝箱」中,咱們只是「開箱即用」。Docker把交付運行環境比做「海運」:OS如同一個貨輪,每個在OS上運行的軟件都如同一個集裝箱,用戶能夠經過標準化手段自由組裝運行環境,同時集裝箱的內容能夠由用戶自定義,也能夠由專業人員製造——這樣交付一個軟件,就是一系列標準化組件集的交付,如同樂高積木,用戶只須要選擇合適的積木組合,最後個標準化組件就是給用戶的應用程序。這就是基於docker的PaaS()產品的原型。
一個完整的Docker有如下幾個部分組成:
DockerClient客戶端
Docker Daemon守護進程
Docker Image鏡像
DockerContainer容器
在docker的網站上介紹了使用docker的典型場景:
Automating the packaging and deployment of applications(應用打包部署自動化)
Creation of lightweight, private PAAS environments(建立輕量、私有的PaaS環境)
Automated testing and continuous integration/deployment(實現自動化測試和持續的集成/部署)
Deploying and scaling web apps, databases and backend services(部署與擴展web app、數據庫和後端服務)
因爲Docker 基於LXC的輕量級虛擬化的特色,相比 KVM 之類虛擬機而言,最明顯的特色就是啓動快,資源佔用小(輕量級)——這正是構建隔離的標準化的運行環境,輕量級的PaaS,構建自動化測試和持續集成環境,以及一切能夠橫向擴展的應用等場景的最佳選擇。
提示:更多關於 Docker 的介紹參考: https://docs.docker.com 。Dockers Github 項目空間是:https://github.com/docker
本小節介紹如何搭建 Docker 環境。
安裝 Docker
去 docker 官網 https://docs.docker.com/install/ 下載對應的操做系統上的安裝包。安裝完畢,打開Docker運行,能夠看到Mac 系統菜單欄上的顯示的 Docker 應用信息以下
圖16-5 Mac 系統菜單欄上的 Docker 圖標
想知道 docker 提供了哪些命令行操做嗎?執行docker help便可看到一個詳細的命令說明。例如,在命令行查看 Docker 版本信息:
$ docker version Client: Version: 17.12.0-ce API version: 1.35 Go version: go1.9.2 Git commit: c97c6d6 Built: Wed Dec 27 20:03:51 2017 OS/Arch: darwin/amd64 Server: Engine: Version: 17.12.0-ce API version: 1.35 (minimum version 1.12) Go version: go1.9.2 Git commit: c97c6d6 Built: Wed Dec 27 20:12:29 2017 OS/Arch: linux/amd64 Experimental: false
查看詳細的 docker 信息
$ docker info Containers: 0 Running: 0 Paused: 0 Stopped: 0 Images: 1 Server Version: 17.12.0-ce …
從倉庫 pull Java 環境鏡像
使用sudo docker pull java命令從 Docker 官方倉庫獲取 Java 運行環境鏡像:
$ sudo docker pull java Password: Using default tag: latest latest: Pulling from library/java ... bb9cdec9c7f3: Pull complete Digest: sha256:c1ff613e8ba25833d2e1940da0940c3824f03f802c449f3d1815a66b7f8c0e9d Status: Downloaded newer image for java:latest
下載完畢以後,能夠經過docker images命令查看鏡像列表:
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE Java latest d23bdf5b1b1b 12 months ago 643MB
能夠看到,本地鏡像中已經有了 java 運行環境。
本節介紹如何把上面的 Spring Boot 項目 Docker 容器化。過程主要分爲以下3步:
1)添加 docker構建插件。
2)配置Dockerfile文件建立自定義的鏡像。
3)構建Docker鏡像。
下面咱們就來分別詳細介紹。
在 Gradle 項目構建配置文件build.gradle 中添加com.palantir.docker插件:
buildscript { ext { kotlinVersion = '1.2.20' springBootVersion = '2.0.0.RC1' } repositories { // gradle-docker plugin repo maven { url "https://plugins.gradle.org/m2/" } ... } dependencies { ... classpath('gradle.plugin.com.palantir.gradle.docker:gradle-docker:0.17.2') } } apply plugin: 'com.palantir.docker' ... docker { name "${project.group}/${jar.baseName}" files jar.archivePath buildArgs(['JAR_FILE': "${jar.archiveName}"]) }
其中,buildArgs(['JAR_FILE': "${jar.archiveName}"]) 中配置的'JAR_FILE': "${jar.archiveName}" 是咱們的 Spring Boot 項目打成 jar包的名稱,會傳遞到Dockerfile文件中使用(下一步驟中將會看到)。
提示:關於Docker 插件com.palantir.docker的介紹參考文檔: https://github.com/palantir/g...
這個插件發佈在https://plugins.gradle.org/m2...,因此咱們添加 maven 倉庫的依賴
repositories { // gradle-docker plugin repo maven { url "https://plugins.gradle.org/m2/" } ... }
gradle-docker提供的版本有:
https://plugins.gradle.org/m2...
Dockerfile文件放置在項目根目錄:
圖16-6 Dockerfile文件放置在項目根目錄
Dockerfile文件內容以下:
FROM java:latest VOLUME /tmp ARG JAR_FILE ADD ${JAR_FILE} app.jar ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]
配置構建參數JAR_FILE,這裏的JAR_FILE是在 build.gradle 中buildArgs中配置的
docker { name "${project.group}/${jar.baseName}" files jar.archivePath buildArgs(['JAR_FILE': "${jar.archiveName}"]) } ADD ${JAR_FILE} app.jar
將文件${JAR_FILE}拷貝到docker container的文件系統對應的路徑app.jar
ENTRYPOINT ["java", "-Djava.security.egd= file:/dev/./urandom", "-jar", "/app.jar"]
Docker container啓動時執行的命令。注意:一個Dockerfile中只能有一條ENTRYPOINT命令。若是多條,則只執行最後一條。
-Djava.security.egd=file:/dev/./urandom
配置 JRE 使用非阻塞的 Entropy Source。SecureRandom generateSeed 使用 /dev/random 生成種子。可是 /dev/random 是一個阻塞數字生成器,若是它沒有足夠的隨機數據提供,它就一直等,這迫使 JVM 等待。經過在 JVM 啓動參數中配置這麼一行:-Djava.security.egd=file:/dev/./urandom 解決這個阻塞問題。
Dockerfile是一個文本格式的配置文件,咱們可使用Dockerfile文件快速建立自定義的鏡像。Dockerfile支持的豐富的運維指令。這些指令分爲4部分:
基礎鏡像信息
維護者信息
鏡像操做指令
容器啓動時的執行指令
...
直接在命令行執行:
$ docker run -p 8080:9000 -t com.easy.springboot/demo_package_and_deploy
便可啓動咱們構建發佈在 Docker 鏡像倉庫中的Spring Boot 應用鏡像了。
咱們的 Spring Boot 應用鏡像運行在 Docker容器沙箱環境中,端口號是9000,做爲外部Host OS環境要訪問這個服務, 須要添加TCP端口映射:把本機8080端口映射到 Docker 容器端口9000,以下圖所示:
圖16-7 把本機8080端口映射到 Docker 容器端口9000
其中:
-p 是將容器的端口9000映射到 docker 所在操做系統的端口8080;
-t 是打開一個僞終端,以便後續能夠進入查看控制檯 log。
使用 docker ps 命令查看運行中的容器:
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 36fbfaf05359 com.easy.springboot/demo_package_and_deploy "java -Djava.securit…" 25 minutes ago Up 25 minutes 0.0.0.0:8080->9000/tcp infallible_kare
……
而後,執行 push 命令便可:
$ docker push com.easy.springboot/demo_package_and_deploy
提示:本節項目源代碼:https://github.com/EasySpring...
本章簡單介紹了Spring Boot項目的打包、分環境運行、生產運維等操做。一般,在企業項目實踐中,會實現一套 Spring Boot應用部署發佈的自動化運維平臺工具。本章還給出了一個完整的 Spring Boot項目 Docker 化的實戰案例。通過前面的學習,相信您已經對如何使用基於 Kotlin 編程語言的 Spring Boot項目開發有了一個比較好的掌握。