Spring Boot 应用
- 
	
基于 Spring Boot 框架的应用,通常会构建成 XXX.jar 文件,执行 java -jar XXX.jar 来运行该应用;
 
Docker 下的 Spring Boot 应用镜像
- 
	
Docker 环境下,通常用 Maven 的 docker-maven-plugin 插件将应用打包成镜像,例如以 java:8u111-jdk 作为基础镜像再加入 jar 文件,这样容器启动的时候执行 java -jar XXX.jar 就能将应用运行起来了;
 
传统思路
- 
	
Spring Boot 应用镜像在 kubernetes 声明为 Pod,即可正常运行;
 - 
	
但是,这是合适的做法么?去 K8S 官网需要一些理论上的指导吧;
 
寻找官方的理论依据
- 
	
官方文档地址:https://kubernetes.io/docs/concepts/workloads/pods/pod-overview/
 - 
	
其中对 Pod 中多个容器的关系描述如下:
 

- 
	
上图红框中提到一个容器基于共享资源对外提供服务,另一个"sidecar"容器负责更新这些共享资源;
 - 
	
在 Kubernetes 中文社区的文档中也对此作了阐述,地址是:http://docs.kubernetes.org.cn/312.html
 

- 
	
在提到 Pod 中的 sidecar 模式时,官方文档用到"relatively advanced"来形容,进一步证实了当下该模式的正确性,如下图:
 

- 
	
具体的实现模型如下图:
 

Spring Boot 应用的 sidecar 设计
- 
	
根据 kubernetes 官方文档的指导,再结合 SpringBoot 应用的特点,我设计出的 sidecar 部署方式如下:
 

- 
	
该应用的业务服务被封装在一个 Pod 定义中,该 Pod 由两个容器组成;
 - 
	
绿色容器是来自 OpenJDK 官方镜像: openjdk:8u181-jre-alpine3.8 ,用 docker history 命令查看体积,几十兆不算大:
 
[root@localhost work]# docker history openjdk:8u181-jre-alpine3.8IMAGE CREATED CREATED BY SIZE COMMENT2e01f547f003 12 days ago /bin/sh -c set -x && apk add --no-cache ... 78.6 MB<missing> 12 days ago /bin/sh -c #(nop) ENV JAVA_ALPINE_VERSION... 0 B<missing> 12 days ago /bin/sh -c #(nop) ENV JAVA_VERSION=8u181 0 B<missing> 7 weeks ago /bin/sh -c #(nop) ENV PATH=/usr/local/sbi... 0 B<missing> 7 weeks ago /bin/sh -c #(nop) ENV JAVA_HOME=/usr/lib/... 0 B<missing> 7 weeks ago /bin/sh -c { echo '#!/bin/sh'; echo 's... 87 B<missing> 7 weeks ago /bin/sh -c #(nop) ENV LANG=C.UTF-8 0 B<missing> 7 weeks ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0 B<missing> 7 weeks ago /bin/sh -c #(nop) ADD file:25c10b1d1b41d46... 4.41 MB
复制代码
- 
	
之所以要用 jre-alpine 版本,是因为 8u181-jdk 版本相比之下大了很多,如下所示:
 
[root@localhost work]# docker history openjdk:8u181-jdkIMAGE CREATED CREATED BY SIZE COMMENT954739b8bdfb 7 days ago /bin/sh -c /var/lib/dpkg/info/ca-certifica... 355 kB<missing> 7 days ago /bin/sh -c set -ex; if [ ! -d /usr/share... 348 MB<missing> 7 days ago /bin/sh -c #(nop) ENV CA_CERTIFICATES_JAV... 0 B<missing> 7 days ago /bin/sh -c #(nop) ENV JAVA_DEBIAN_VERSION... 0 B<missing> 3 weeks ago /bin/sh -c #(nop) ENV JAVA_VERSION=8u181 0 B<missing> 3 weeks ago /bin/sh -c #(nop) ENV JAVA_HOME=/docker-j... 0 B<missing> 3 weeks ago /bin/sh -c ln -svT "/usr/lib/jvm/java-8-op... 33 B<missing> 3 weeks ago /bin/sh -c { echo '#!/bin/sh'; echo 's... 87 B<missing> 3 weeks ago /bin/sh -c #(nop) ENV LANG=C.UTF-8 0 B<missing> 3 weeks ago /bin/sh -c apt-get update && apt-get insta... 2.21 MB<missing> 3 weeks ago /bin/sh -c apt-get update && apt-get insta... 142 MB<missing> 3 weeks ago /bin/sh -c set -ex; if ! command -v gpg >... 7.8 MB<missing> 3 weeks ago /bin/sh -c apt-get update && apt-get insta... 23.2 MB<missing> 3 weeks ago /bin/sh -c #(nop) CMD ["bash"] 0 B<missing> 3 weeks ago /bin/sh -c #(nop) ADD file:b3598c18dc39584... 101 MB
复制代码
- 
	
红色容器的镜像是用 Spring Boot 应用构建出来的,稍后再详细整个过程;
 - 
	
在 kubernetes 环境,这两个容器会挂载同一个 Volume,红色容器将 jar 放在此处,绿色容器使用此处的 jar;
 - 
	
红色容器用来提供 jar,没有进程需要保持运行状态,很适合设置为 Init Container 类型;
 - 
	
绿色容器的 java 进程是长久运行的;
 - 
	
以上就是整体设计思路,接下来咱们就来实战吧,分三步完成:
 
实战步骤列举
- 
	
本次实战分为以下几部分组成:
 - 
	
开发 Spring Boot 应用;
 - 
	
制作 Docker 镜像,做两个版本,以便验证升级;
 - 
	
编写 yaml 文件;
 - 
	
在 kubernetes 环境部署,验证;
 - 
	
升级版本,验证;
 
环境和版本信息
- 
	
编译构建的 jdk 和运行的 jre 都用 1.8 版本;
 - 
	
maven:3.3.3;
 - 
	
spring boot:2.1.0.RELEASE;
 - 
	
docker:1.13.1;
 - 
	
kubernetes:1.12.2;
 
- 
	
本次实战一共有四台 CentOS7 机器,基本信息如下:
 

- 
	
kubernetes 环境由 localhost、node1、node2 三台机器组成,maven 负责编译构建、生成镜像、上传到镜像仓库等操作;
 
开发 Spring Boot 应用
- 
	
这是个简单的 Spring Boot 应用,对外提供一个 http 接口,返回一个字符串;
 - 
	
您可以选择直接从 GitHub 下载这个工程的源码,地址和链接信息如下表所示:
 

- 
	
这个 git 项目中有多个文件夹,本章源码在 springbootsidecardemo 这个文件夹下,如下图红框所示:
 

- 
	
您也可以随本文一起来开发这个应用:
 - 
	
应用名为 springbootsidecardemo,是用 maven 构建的,JDK 使用 1.8,Spring Boot 版本 2.1.0.RELEASE;
 - 
	
应用的 pom.xml 如下,为了构建 Docker 镜像使用了 docker-maven-plugin 插件,该插件具体的配置请参照下面的注释:
 
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.bolingcavalry</groupId><artifactId>springbootsidecardemo</artifactId><version>0.0.1</version><packaging>jar</packaging><name>springbootsidecardemo</name><description>Demo project for Spring Boot sidecard demo in K8S</description><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.0.RELEASE</version><relativePath/> <!-- lookup parent from repository --></parent><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><java.version>1.8</java.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><finalName>app</finalName><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin><!--新增的docker maven插件--><plugin><groupId>com.spotify</groupId><artifactId>docker-maven-plugin</artifactId><version>0.4.12</version><!--docker镜像相关的配置信息--><configuration><!--镜像名,这里用工程名--><imageName>bolingcavalry/${project.artifactId}</imageName><!--TAG,这里用工程版本号--><imageTags><imageTag>${project.version}</imageTag></imageTags><!--镜像的FROM,使用busybox--><baseImage>busybox</baseImage><!--构建镜像的配置信息--><resources><resource><targetPath>/</targetPath><directory>${project.build.directory}</directory><include>app.jar</include></resource></resources></configuration></plugin></plugins></build></project>
复制代码
- 
	
上面的配置有一处需要注意,就是基础镜像的选择(就是 baseImage 节点中的内容),我用了 busybox ,用它是因为够小,来看 docker 镜像仓库中的描述,地址是 https://hub.docker.com/_/busybox/:
 

- 
	
看到这里,可能会有朋友问"为什么不用 scratch?它比 busybox 更小",scratch 虽小,但不带基本的 shell 命令,例如 cp 命令,而后容器启动时要用 cp 命令对文件做复制操作,因此只能选择 busybox 了;
 - 
	
Controller 类的代码也很简单:
 
package com.bolingcavalry.springbootsidecardemo.controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import java.util.Date;/*** @Description: 一个最普通的Controller,hello接口返回一个字符串并带上当前时间* @author: willzhao E-mail: zq2599@gmail.com* @date: 2018/11/6 14:15*/@RestControllerpublic class HelloController {@RequestMapping(value = "/hello")public String hello(){return "Hello version 1.0 " + new Date();}}
复制代码
制作应用的 Docker 镜像
- 
	
请确保您当前环境的 maven 和 Docker 都已经 OK 了;
 - 
	
在应用的 pom.xml 所在目录执行如下命令即可构建 Docker 镜像:
 
mvn clean package -U -DskipTests docker:build
复制代码
- 
	
构建成功的控制台输出如下:
 
[INFO] Building image bolingcavalry/springbootsidecardemo[INFO] Building image bolingcavalry/springbootsidecardemoStep 1/2 : FROM busybox---> 59788edf1f3eStep 2/2 : ADD /app.jar //---> 8105c9ac033bRemoving intermediate container fdc62513abf6Successfully built 8105c9ac033b[INFO] Built bolingcavalry/springbootsidecardemo[INFO] Tagging bolingcavalry/springbootsidecardemo with 0.0.1[INFO] ------------------------------------------------------------------------[INFO] BUILD SUCCESS[INFO] ------------------------------------------------------------------------[INFO] Total time: 5.354 s[INFO] Finished at: 2018-11-06T05:07:08-08:00[INFO] Final Memory: 42M/225M[INFO] ------------------------------------------------------------------------
复制代码
- 
	
构建成功后用 docker history 命令查看镜像,如下,三个 layer 组成:
 
root@maven:~# docker history bolingcavalry/springbootsidecardemo:0.0.1IMAGE CREATED CREATED BY SIZE COMMENT8105c9ac033b 46 minutes ago /bin/sh -c #(nop) ADD file:909ca8e9c8898cd... 16.6 MB59788edf1f3e 4 weeks ago /bin/sh -c #(nop) CMD ["sh"] 0 B<missing> 4 weeks ago /bin/sh -c #(nop) ADD file:63eebd629a5f755... 1.15 MB
复制代码
- 
	
去工程的 target 目录下看看构建的 app.jar 文件,如下所示,也是 16 兆,所以这个镜像已经被做到最小了,相对于以前那种 JAVA 环境+jar 文件的镜像,这个镜像更易于下载和上传:
 
root@maven:/usr/local/work/github/blog_demos/springbootsidecardemo/target# ls -altotal 16276drwxr-xr-x 9 root root 4096 Nov 6 05:08 .drwxr-xr-x 5 root root 4096 Nov 6 05:29 ..-rw-r--r-- 1 root root 16621351 Nov 6 05:08 app.jar-rw-r--r-- 1 root root 4432 Nov 6 05:08 app.jar.originaldrwxr-xr-x 3 root root 4096 Nov 6 05:08 classesdrwxr-xr-x 2 root root 4096 Nov 6 05:08 dockerdrwxr-xr-x 3 root root 4096 Nov 6 05:08 generated-sourcesdrwxr-xr-x 3 root root 4096 Nov 6 05:08 generated-test-sourcesdrwxr-xr-x 2 root root 4096 Nov 6 05:08 maven-archiverdrwxr-xr-x 3 root root 4096 Nov 6 05:08 maven-statusdrwxr-xr-x 3 root root 4096 Nov 6 05:08 test-classes
复制代码
- 
	
执行 docker push 命令,将镜像推送到镜像仓库中,我这里是推送到了 hub.docker.com,您可以根据实际情况来执行,例如私有仓库、阿里云等都可以,当然了,如果当前机器就是 K8S 的机器就不用推送了,毕竟此镜像就是在 K8S 环境用的;
 - 
	
如果觉得推送到仓库太慢,或者从仓库下载太慢,也可以使用文件导入导出的方式,具体操作如下:
 
#将镜像导出为tar文件docker save 2e01f547f003 > 1.tar###将tar文件还原为镜像docker load < 1.tar###还原后的镜像的名称和tag都不对,要用tag命令来设置docker tag 8105c9ac033b bolingcavalry/springbootsidecardemo:0.0.1
复制代码
制作应用升级版的 Docker 镜像
- 
	
为了验证 K8S 下的应用升级,做好 tag 为 0.0.1 的镜像之后,我们改动应用代码,把 pom.xml 中的版本改成 0.0.2,然后再做个镜像,这样稍后在 K8S 就能验证 Pod 升级了;
 - 
	
修改 HelloController.java 的源码,hello 方法返回的字符串,之前是 Hello version 1.0 ,现在改成 Hello version 2.0 ;
 - 
	
修改 pom.xml 中的 version 节点,之前是 0.0.1 ,现在改成 0.0.2 ,由于我们已配置了镜像 tag 就是工程版本,因此新构建的镜像 tag 会是 0.0.2;
 - 
	
再次执行 maven 命令构建,然后推送到镜像仓库;
 - 
	
此时我们有两个镜像了:
 
root@maven:~# docker images | grep sidecarbolingcavalry/springbootsidecardemo 0.0.2 f6ba01c33388 11 hours ago 17.8 MBbolingcavalry/springbootsidecardemo 0.0.1 8105c9ac033b 11 hours ago 17.8 MB
复制代码
- 
	
现在镜像已经 OK,该准备部署到 kubernetes 环境了;
 
编写 yaml 文件
- 
	
在可以执行 kubectl 命令的机器上编写配置文件,Pod 的配置 javaweb-deploy.yaml 文件内容如下:
 
apiVersion: extensions/v1beta1kind: Deploymentmetadata:name: javawebspec:replicas: 1template:metadata:labels:name: javawebspec:initContainers:- image: bolingcavalry/springbootsidecardemo:0.0.1name: appjarcommand: ["cp", "/app.jar", "/app"]volumeMounts:- mountPath: /appname: app-volumecontainers:- image: openjdk:8u181-jre-alpine3.8name: openjdk8u181command: ["java","-jar","/webapp/app.jar"]volumeMounts:- mountPath: /webappname: app-volumeports:- containerPort: 8080volumes:- name: app-volumeemptyDir: {}
复制代码
- 
	
从上述配置中,有两处需要注意:
 - 
	
第一,应用镜像被设置为 initConteiners 类型的容器,被设置执行 cp 命令将自己的 app.jar 文件复制到共享的 Volume;
 - 
	
第二,一直运行用于提供服务的容器,来自 openjdk 镜像的 java 进程,该进程加载的 jar 文件就是共享的 Volume 中的 app.jar;
 - 
	
为了能在浏览器上访问该应用,再部署个 service,其配置文件 javaweb-svc.yaml 的内容如下:
 
apiVersion: v1kind: Servicemetadata:name: javawebspec:type: NodePortports:- port: 8080nodePort: 30008selector:name: javaweb
复制代码
- 
	
启动 pod 和 service,在前面的 yaml 文件所在目录执行命令如下:
 
kubectl create -f javaweb-deploy.yaml \&& kubectl create -f javaweb-svc.yaml
复制代码
- 
	
service 的类型是 NodePort,因此可以通过 Node 节点的 IP 地址访问服务,我这边 Node 地址为 192.168.119.159,因此访问地址就是:http://192.168.119.159:30008/hello ,在浏览器访问返回如下内容:
 

- 
	
符合预期,证明主容器加 sidecar 容器的组合方式是可以正常工作的,接下来试试升级;
 
升级版本
- 
	
接下来模拟生产环境的应用升级,前面准好了两个版本的应用镜像:0.0.1 和 0.0.2,现在 K8S 环境运行的是 0.0.1,咱们将其升级为 0.0.2:
 - 
	
修改 javaweb-deploy.yaml 文件中镜像的 tag,从 0.0.1 改成 0.0.2,如下图红框所示:
 

- 
	
在 javaweb-deploy.yaml 文件所在目录执行如下命令即可完成升级:
 
kubectl apply -f javaweb-deploy.yaml
复制代码
- 
	
在浏览器访问 http://192.168.119.159:30008/hello,返回如下内容,可见的确是修改后的应用逻辑:
 

- 
	
升级成功,符合预期;
 
小结
- 
	
sidecar 模式下,仅需更新应用 jar 打包的镜像,这个镜像可以做到极小;
 - 
	
提供 java 进程的镜像是固定的,在 K8S 环境下,一个 Node 上实际运行着多种 pod,如果他们的 java 进程都由一个镜像提供,其好处是不言而喻的;
 - 
	
Spring Boot 应用的运行,是由 java 进程与应用 jar 文件组成的,很适合 sidecar 模式,通过容器解耦,通过 pod 对外服务;
 - 
	
至此,Spring Boot 应用在 kubernetes 的 sidecar 设计与实战就全部完成了,希望此文能助您将应用以 sidecar 模式部署在 kubernetes,如果您发现我对官网的指导内容理解有误或有偏差,欢迎您的热心指正,谢谢!
 
                  
                  
                  
                  
                            
                            
                            
本文介绍了如何在Kubernetes环境中采用sidecar模式部署Spring Boot应用。通过官方文档的指导,设计了一个由两个容器组成的Pod,其中一个作为Init Container负责复制jar文件,另一个作为长期运行的服务容器。在升级过程中,仅更新应用jar的镜像,实现了应用的无缝升级。
          
      
          
                
                
                
                
              
                
                
                
                
                
              
                
                
              
            
                  
					698
					
被折叠的  条评论
		 为什么被折叠?
		 
		 
		
    
  
    
  
            


            