Spring Boot 應用 Docker 化 《Spring Boot 2.0極簡教程》(陳光劍)

Spring Boot 應用 Docker 化

《Spring Boot 2.0極簡教程》(陳光劍)
—— 基於 Gradle + Kotlin的企業級應用開發最佳實踐

前面的章節中,咱們都是在IDE環境中開發運行測試 Spring Boot 應用程序。在開發測試發佈整個軟件生命週期的過程當中,咱們一般須要完成打包部署發佈到平常、預發、線上機器運行等運維相關工做。
本章前半部分介紹 Spring Boot 應用的打包和部署,後半部分重點介紹如何使用 Docker 來構建部署運行 Spring Boot 應用。html

1.1 準備工做

首先,使用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
    }
}

1.2 項目打包成可執行 jar

在 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

1.3 定製配置文件啓動應用

咱們也能夠在啓動的時候選擇讀取不一樣的配置文件。例如,在項目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)

1.4 項目打包成 war 包

在上面建立的項目中,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...

1.5 Spring Boot應用運維

本節簡單介紹一些 Spring Boot 應用的生產運維的一些內容。

1.5.1 查看JVM參數的值

使用命令:

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...

1.5.2 應用重啓

要想重啓應用,要首先找到該應用的 java進程,而後kill掉 java 進程。完成這個邏輯的shell 腳本以下:

kill -9 $(ps -ef|grep java|awk '{print $2}')

而後,再使用命令行從新啓動應用便可。

1.6 使用 Docker 構建部署運行Spring Boot應用

本節介紹如何使用 Docker 來構建部署 Spring Boot 應用。

1.6.1 Docker 簡介

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

1.6.2 環境搭建

本小節介紹如何搭建 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 運行環境。

1.7 Spring Boot 項目 Docker化實戰

本節介紹如何把上面的 Spring Boot 項目 Docker 容器化。過程主要分爲以下3步:
1)添加 docker構建插件。
2)配置Dockerfile文件建立自定義的鏡像。
3)構建Docker鏡像。
下面咱們就來分別詳細介紹。

1.7.1 添加 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...

1.7.2 配置 Dockerfile 文件建立自定義的鏡像

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部分:

 基礎鏡像信息
 維護者信息
 鏡像操做指令
 容器啓動時的執行指令
...

1.7.5 啓動 Docker 應用鏡像運行

直接在命令行執行:

$ docker run -p 8080:9000 -t com.easy.springboot/demo_package_and_deploy

便可啓動咱們構建發佈在 Docker 鏡像倉庫中的Spring Boot 應用鏡像了。

1.7.6 端口映射

咱們的 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...

1.8 本章小結

本章簡單介紹了Spring Boot項目的打包、分環境運行、生產運維等操做。一般,在企業項目實踐中,會實現一套 Spring Boot應用部署發佈的自動化運維平臺工具。本章還給出了一個完整的 Spring Boot項目 Docker 化的實戰案例。通過前面的學習,相信您已經對如何使用基於 Kotlin 編程語言的 Spring Boot項目開發有了一個比較好的掌握。

相關文章
相關標籤/搜索