Kubernetes集群部署SpringBoot项目常见配置及常用处理方式

部分内容参考地址:
CSDN:

一、必要条件

  1. K8S环境机器做部署用,推荐一主双从。推荐安装文档
  2. Docker Harbor私有仓库,准备完成后在需要使用仓库的机器docker login。
  3. 开发机器需要Docker环境,build及push使用

二、项目配置

2.1 maven配置

1. properties配置

 <properties>
     <docker.image.prefix>pasq</docker.image.prefix>
     <!-- docker harbor地址 -->
     <docker.repostory>192.168.1.253:8081</docker.repostory>
 </properties>

2. plugins配置

  <properties>
  	 	<docker.repostory>192.168.0.10:1180</docker.repostory>
        <docker.registry.name>test</docker.registry.name>
        <docker.image.tag>1.0.0</docker.image.tag>
        <docker.maven.plugin.version>1.4.10</docker.maven.plugin.version>
  </properties>

<build>
  		<finalName>test-starter</finalName>
		<plugins>
            <plugin>
			    <groupId>org.springframework.boot</groupId>
			    <artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
			
			<!-- docker的maven插件,官网:https://github.com/spotify/docker‐maven‐plugin -->
			<!-- Dockerfile maven plugin -->
			<plugin>
			    <groupId>com.spotify</groupId>
			    <artifactId>dockerfile-maven-plugin</artifactId>
			    <version>${docker.maven.plugin.version}</version>
			    <executions>
			        <execution>
			        <id>default</id>
			        <goals>
			            <!--如果package时不想用docker打包,就注释掉这个goal-->
			            <goal>build</goal>
			            <goal>push</goal>
			        </goals>
			        </execution>
			    </executions>
			    <configuration>
			    	<contextDirectory>${project.basedir}</contextDirectory>
			        <!-- harbor 仓库用户名及密码-->
			        <useMavenSettingsForAuth>useMavenSettingsForAuth>true</useMavenSettingsForAuth>
			        <repository>${docker.repostory}/${docker.registry.name}/${project.artifactId}</repository>
			        <tag>${docker.image.tag}</tag>
			        <buildArgs>
			            <JAR_FILE>target/${project.build.finalName}.jar</JAR_FILE>
			        </buildArgs>
			    </configuration>
			</plugin>

        </plugins>
        
		<resources>
			<!-- 指定 src/main/resources下所有文件及文件夹为资源文件 -->
			<resource>
				<directory>src/main/resources</directory>
				<targetPath>${project.build.directory}/classes</targetPath>
				<includes>
					<include>**/*</include>
				</includes>
				<filtering>true</filtering>
			</resource>
		</resources>
	</build>

2.2 服务镜像相关配置

镜像可以分为基础镜像和应用镜像。

1. 基础镜像

基础镜像要求体积尽量小,方便拉取,同时安装一些必要的软件,方便后期进入容器内排查问题,我们需要准备好服务运行的底层系统镜像,比如 Centos、Ubuntu 等常见 Linux 操作系统,然后基于该系统镜像,构建服务运行需要的环境镜像,比如一些常见组合:Centos + JdkCentos + Jdk + TomcatCentos + nginx 等,由于不同的服务运行依赖的环境版本不一定一致,所以还需要制作不同版本的环境镜像,例如如下基础镜像版本。

  • Centos6.5 + Jdk1.8: registry.docker.com/baseimg/centos-jdk:6.5_1.8
  • Centos7.5 + Jdk1.8: registry.docker.com/baseimg/centos-jdk:7.5_1.8
  • Centos7.5 + Jdk1.7: registry.docker.com/baseimg/centos-jdk:7.5_1.7
  • Centos7 + Tomcat8 + Jdk1.8: registry.docker.com/baseimg/centos-tomcat-jdk:7.5_8.5_1.8
  • Centos7 + Nginx: registry.docker.com/baseimg/centos-tomcat-jdk:7.5_1.10.2

这样,就可以标识该基础镜像的系统版本及软件版本,方便后边选择对应的基础镜像来构建应用镜像。基础镜像的制作方法之一,可以参考 使用 febootstrap 制作自定义基础镜像 方式。

2. 应用镜像

有了上边的基础镜像后,就很容易构建出对应的应用镜像了,例如一个简单的应用镜像 Dockerfile 如下:

FROM registry.docker.com/baseimg/centos-jdk:7.5_1.8

COPY app-name.jar /opt/project/app.jar
EXPOSE 8080
ENTRYPOINT ["/java", "-jar", "/opt/project/app.jar"]

当然,这里我建议使用另一种方式来启动服务,将启动命令放在统一 shell 启动脚本执行,例如如下Dockerfile 示例:

#基础镜像,如果本地仓库没有,会从远程仓库拉取
FROM registry.docker.com/baseimg/centos-jdk:7.5_1.8
#编译后的jar包copy到容器中创建到目录内
COPY app-name.jar /opt/project/app.jar
COPY entrypoint.sh /opt/project/entrypoint.sh
EXPOSE 8080
#指定容器启动时要执行的命令
ENTRYPOINT ["/bin/sh", "/opt/project/entrypoint.sh"]

将服务启动命令配置到 entrypoint.sh,这样我们可以扩展做很多事情,比如启动服务前做一些初始化操作等,还可以向容器传递参数到脚本执行一些特殊操作,而且这里变成脚本来启动,这样后续构建镜像基本不需要改 Dockerfile 了。

#!/bin/bash
# do other things here
java -jar $JAVA_OPTS /opt/project/app.jar $1  > /dev/null 2>&1

上边示例中,我们就注入 $JAVA_OPTS 环境变量,来优化 JVM 参数,还可以传递一个变量,这个变量大家应该就猜到了,就是服务启动加载哪个配置文件参数,例如:--spring.profiles.active=prod 那么,在 Deployment 中就可以通过如下方式配置了:

...
spec:
  containers:
    - name: project-name
      image: registry.docker.com/project/app:v1.0.0
      args: ["--spring.profiles.active=prod"]
      env:
	   - name: JAVA_OPTS
	     value: "-XX:PermSize=512M -XX:MaxPermSize=512M -Xms1024M -Xmx1024M..."
...

是不是很方便,这里可扩展做的东西还很多,根据项目需求来配置。

2.3 构建镜像并推送

  1. 构建镜像,执行如下命令
    插件编译
    构建镜像日志如下
    编译日志
  2. 完成后docker images可以查看打包的镜像
    在这里插入图片描述
  3. 命令窗口执行docker push REPOSITORY推送至docker harbor
    推送
    docker harbor可以查看到推送的镜像
    dockerharbor

2.4 Jenkins配置发布项目

定位到Jenkins的“构建模块”,使用Execute Shell来构建发布项目到K8S集群。

执行的命令依次如下所示。

#删除本地原有的镜像,不会影响Harbor仓库中的镜像
docker rmi 192.168.0.10:1180/test/test-starter:1.0.0
#使用Maven编译、构建Docker镜像,执行完成后本地Docker容器中会重新构建镜像文件
/usr/local/maven-3.6.3/bin/mvn -f ./pom.xml clean install -Dmaven.test.skip=true
#登录 Harbor仓库
docker login 192.168.0.10:1180 -u binghe -p Binghe123
#上传镜像到Harbor仓库
docker push 192.168.0.10:1180/test/test-starter:1.0.0
#停止并删除K8S集群中运行的
/usr/bin/kubectl delete -f test.yaml
#将Docker镜像重新发布到K8S集群
/usr/bin/kubectl apply -f test.yaml

2.5 服务日志输出处理

对于日志处理,之前我们一般会使用 Log4jLogstash 等日志框架将日志输出到服务器指定目录,容器化部署后,日志会生成到容器内某个配置的目录上,外部是没法访问的,所以需要将容器内日志挂载到宿主机某个目录 (例如:/opt/logs 目录),这样方便直接查看,或者配置 FilebeatFluent 等工具抓取到 Elasticsearch 来提供日志查询分析。在 Deployment 配置日志挂载方式也很简单,配置如下:

...
    volumeMounts:
    - name: app-log
      mountPath: /data/logs/serviceA  #log4j 配置日志输出到指定目录
...
	volumes:
    - name: app-log
      hostPath:
        path: /opt/logs #宿主机指定目录

这里有个地方需要特别注意一下:服务日志要关闭 Console 输出,避免直接输出到控制台。默认 Docker 会记录控制台日志到宿主机指定目录,日志默认输出到 /var/lib/docker/containers/<container_id>/<container_id>-json.log,为了避免出现日志太多,占用磁盘空间,需要关闭 Console 输出并定期清理日志文件。

三、K8S部署

当准备好镜像文件后,要部署到Kubernetes就非常容易了,只需要一个yaml格式的文件即可,这个文件能描述你所需要的组件,如DeploymentServiceIngress等。

部署前的一些准备工作(可设置docker harbor远程登录并docker login)

K8S 在部署服务前,需要做一些准备工作,例如提前创建好对应的 Namespace,避免首次直接创建 Deployment 出现 Namespace 不存在而创建失败。如果我们使用的私有镜像仓库,那么还需要生成 Docker Repository 登录认证 Secret,用来注入到 Pod 内拉取镜像时认证需要。

# 包含登录认证信息的 Secret
apiVersion: v1
kind: Secret
metadata:
  name: docker-regsecret
type: kubernetes.io/dockerconfigjson
data:
  .dockerconfigjson: InN1bmRhbmRhbjE4MDUyOEBjcmVkaXRoYdfe3JhdXRocyI6eyJyZWdpc3RyeS1pb

# Deployment 中注入该 Secret
	imagePullSecrets:
    - name: docker-regsecret

3.1 创建springboot.yaml文件如下

apiVersion: v1
kind: Service   # Kind类型有:`Deployment`、`Service`、`Pod`、`Ingress`等
metadata:
  name: springboot
  namespace: default   # 命名空间,默认default
  labels:
    app: springboot
spec:
  type: NodePort
  ports:
  - port: 8080
    name: springboot
    protocol: TCP
    targetPort: 8080
    nodePort: 30090 # service对外开放端口
  selector:
    app: springboot
---
apiVersion: apps/v1
kind: Deployment  # 对象类型
metadata:
  name: springboot # 名称
  labels:
    app: springboot # 标注 
spec:
  replicas: 3 # 运行容器的副本数,修改这里可以快速修改分布式节点数量
  selector:
    matchLabels:
      app: springboot
  template:
    metadata:
      labels:
        app: springboot
    spec:
      containers: # docker容器的配置
        - name: springboot
        image: 192.168.1.253:8081/pasq/dockertest:0.0.1 # pull镜像的地址 ip:prot/dir/images:tag
        imagePullPolicy: IfNotPresent # pull镜像时机,
        ports:
          - containerPort: 8080 # 容器对外开放端口

Kind:类型,有DeploymentServicePodIngress等,非常丰富;

metadata:用于定义一些组件信息,如名字、标签等;

labels:标签功能,非常有用,用于选择关联;但label不提供唯一性,可以使用组合来选择;

nodePort:对于需要给外部暴露的服务,有三种方式:NodePortsLoadBalancerIngress,这里使用NodePorts;需要注意的是,默认它的端口范围是[3000-32767],需要其它范围则需要修改相关参数。

3.2 运行kubectl create -f springboot.yaml创建Deployment

kubectl create -f springboot.yaml

完成后执行kubectl get pods如下图,可以看到启动了三个pod
getpods

通过命令检查是否启动成功:

kubectl get deployment

kubectl get service

kubectl get pod

3.3 运行kubectl logs -f podsname查看日志

kubectl logs -f springboot

新开窗口分别查看3个pod的日志,然后访问k8s master节点IP+service对外开放端口访问springboot应用,我这里使用http://192.168.1.250:30090/test/test, 多刷新几次可以看到pod直接做了负载,如下图:
pods1:
pods1
pods2:
pods2
pods3:
pods3
运行kubectl delete -f dockertest.yaml可以删除pods与service
修改dockertest.ymal 中replicas数量后,运行kubectl apply -f dockertest.yaml可以扩容或收缩副本数量

3.4 结束一个pod

Kubernetes最小管理元素并不是容器,而是Pod

$ kubectl delete pod pkslow-springboot-deployment-68dffc6795-89xww
pod "pkslow-springboot-deployment-68dffc6795-89xww" deleted

$ kubectl get pod
NAME                                            READY   STATUS    RESTARTS   AGE
pkslow-springboot-deployment-68dffc6795-874tp   1/1     Running   0          13m
pkslow-springboot-deployment-68dffc6795-gpw67   1/1     Running   0          46s

可以发现,删除了其它一个Pod后,会自动为我们新生成一个Pod,这样能提高整个服务的高可用。

3.5 杀死一个容器

探索一下如果杀死一个容器实例,会有什么反应。

$ docker ps
$ docker rm -f 57869688a226
57869688a226

$ docker ps

经实验,杀死一个容器后,也会自动为我们重新生成一个容器实例。而Pod并不会变化,也不会重新生成。

3.6 快速扩容

用户请求突增,服务要撑不住了,这时需要增加Pod的个数。只需要修改yaml配置文件的replicas,将它更新为replicas: 4。然后执行以下命令:

$ kubectl apply -f pksow-springboot.yaml

查看Dashboard,在原有两个Pod的基础上,增加了两个。

四、容器服务访问处理

首先需要提供容器服务需要暴露的目标端口号,例如 HttpHttpsGrpc 等服务端口,创建 Service 时需要指定匹配的容器端口号,Deployment 中配置容器暴露端口配置如下:

	ports:
    - containerPort: 8080
      name: http
      protocol: TCP
    - containerPort: 443
      name: https
      protocol: TCP
    - containerPort: 18989
      name: dubbo
      protocol: TCP

4.2、服务对内对外访问方式选择

K8S Service 暴露服务类型有三种:ClusterIPNodePortLoadBalancer,三种类型分别有不同的应用场景。

  • 对内服务发现,可以使用 ClusterIP 方式对内暴露服务,因为存在 Service 重新创建 IP 会更改的情况,所以不建议直接使用分配的 ClusterIP 方式来内部访问,可以使用 K8S DNS 方式解析,DNS 命名规则为:<svc_name>.<namespace_name>.svc.cluster.local,按照该方式可以直接在集群内部访问对应服务。
  • 对外服务暴露,可以采用 NodePortLoadBalancer 方式对外暴露服务,NodePort 方式使用集群固定 IP,但是端口号是指定范围内随机选择的,每次更新 Service 该 Port 就会更改,不太方便,当然也可以指定固定的 NodePort,但是需要自己维护 Port 列表,也不方便。LoadBalancer 方式使用集群固定 IPNodePort,会额外申请申请一个负载均衡器来转发到对应服务,但是需要底层平台支撑。如果使用 AliyunGCE 等云平台商,可以使用该种方式,他们底层会提供 LoadBalancer 支持,直接使用非常方便。

以上方式或多或少都会存在一定的局限性,所以建议如果在公有云上运行,可以使用 LoadBalancerIngress 方式对外提供服务,私有云的话,可以使用 Ingress 通过域名解析来对外提供服务。Ingress 配置使用,可以参考 初试 Kubernetes 暴漏服务类型之 Nginx Ingress初试 Kubernetes 集群中使用 Traefik 反向代理 文章。

五、服务健康监测配置

K8s 提供存活探针和就绪探针,来实时检测服务的健康状态,如果健康检测失败,则会自动重启该 Pod 服务,检测方式支持 exechttpGettcpSocket 三种。对于 Spring Boot 后端 API 项目,建议采用 httpGet 检测接口的方式,服务提供特定的健康检测接口,如果服务正常则返回 200 状态码,一旦检测到非 200 则会触发自动重启机制。K8S 健康监测配置示例如下:

 livenessProbe: # 是否存活检测
    failureThreshold: 3
    httpGet:
      path: /api/healthz
      port: 8080
      scheme: HTTP
    initialDelaySeconds: 300
    periodSeconds: 60
    successThreshold: 1
    timeoutSeconds: 2
  readinessProbe: # 是否就绪检测
    failureThreshold: 1
    httpGet:
      path: /api/healthz
      port: 8080
      scheme: HTTP
    periodSeconds: 5
    successThreshold: 1
    timeoutSeconds: 2 

六、服务 CPU & Mem 请求/最大值配置

K8S 在部署 Deployment 时,可以为每个容器配置最小及最大 CPU & Mem 资源限制,这个是很有必要的,因为不配置资源限制的话,那么默认该容器服务可以无限制使用系统资源,这样如果服务异常阻塞或其他原因,导致占用系统资源过多而影响其他服务的运行,同时 K8S 集群资源不足时,会优先干掉那些没有配置资源限制的服务。当然,请求资源量和最大资源量要根据服务启动实际需要来配置,如果不清楚需要配置多少,可以先将服务部署到 K8S 集群中,看正常调用时监控页面显示的请求值,在合理配置。

resources:
  limits:
    cpu: "1000m"
    memory: "1024Mi"
  requests:
    cpu: "500m"
    memory: "512Mi"

七、K8S集群部署其他注意事项

灵活使用 ConfigMap 资源类型

K8S 提供 ConfigMap 资源类型来方便灵活控制配置信息,我们可以将服务需要的一些 ENV 信息或者配置信息放到 ConfigMap 中,然后注入到 Pod 中即可使用,非常方便。ConfigMap 使用方式有很多种,这里建议大家可以将一些经常更改的配置放到 ConfigMap 中,例如我在实际操作中,就发现有的项目 nginx.conf 配置,还有配置的 ENV 环境变量信息经常变动,那么就可以放在 ConfigMap 中配置,这样 Deployment 就不需要重新部署了。

# 包含 nginx.conf 配置的 ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-conf-configmap
  data:
    www.conf: |
      server {
        listen 80;
        server_name 127.0.0.1

        root /opt/project/nginx/html/;
        error_page 405 =200 $uri;

        access_log  /opt/project/nginx/logs/http_accesss.log  main;
        error_log   /opt/project/nginx/logs/http_error.log;
      }

# 将 ConfigMap 挂载到容器指定目录
volumes:
- name: nginx-config
  configMap:
    defaultMode: 420
    name: nginx-conf-configmap

这里有一个使用 ConfigMap 优雅加载 Spring Boot 配置文件实现方式的示例,可以参考 这里

Deployment 资源部署副本数及滚动更新策略

K8S 建议使用 Deployment 资源类型启动服务,使用 Deployment 可以很方便的进行滚动更新、扩缩容/比例扩容、回滚、以及查看更新版本历史记录等。所以建议副本数至少 2 个,保证服务的可用性,要根据服务实际访问量,来合理配置副本数,过多造成资源浪费,过少造成服务负荷高响应慢的问题,当然也可以根据服务访问量,灵活扩缩容副本数。

Deployment 更新策略有 RecreateRollingUpdate 两种,Recreate 方式在创建出新的 Pod 之前会先杀掉所有已存在的 Pod,这种方式不友好,会存在服务中断,中断的时间长短取决于新 Pod 的启动就绪时间。RollingUpdate 滚动更新方式,通过配合指定 maxUnavailablemaxSurge 参数来控制更新过程,使用该策略更新时会新启动 replicas 数量的 Pod,新 Pod 启动完毕后,在干掉旧 Pod,如果更新过程中,新 Pod 启动失败,旧 Pod 依旧可以提供服务,直到启动完成,服务才会切到新 Pod,保证服务不会中断,建议使用该策略。

replicas: 2
strategy:
  rollingUpdate:
    maxSurge: 1  #也可以按比例配置,例如:20%
    maxUnavailable: 0 #也可以按比例配置,例如:20%
  type: RollingUpdate

要保证 K8S 资源 CPU & Mem & Disk 资源够用

要时刻关注 K8S 集群资源使用情况,保证系统资源够集群使用,否则会出现因为 CPU 、Mem、Disk 不够用导致 Deployment 调度失败的情况。

K8S 集群配置项优化

K8S 集群创建每个 Namespaces 时默认会创建一个名称为 default 的 ServiceAccount,该 ServiceAccount 包含了名称为 default-token-xxxx 的 Secret,该 Secret 包含集群 api-server 使用的根 CA 证书以及认证用的令牌 Token,而且默认新创建 Pod 时会自动将该 ServiceAccount 包含的信息自动注入到 Pod 中,在 Pod 中可以直接使用这些认证信息连接集群执行 api 相关操作,这样会存在一定的风险,所以建议使用 automountServiceAccountToken: false 配置来关闭自动注入。

另一个配置 progressDeadlineSeconds,该配置用来指定在升级或部署时,由于各种原因导致卡住(还没有表明升级或部署失败),等待的 deadline 秒数,如果超过该 deadline 时间,那么将上报并标注 Deployment 状态为 False 并注明失败原因,然后 Deployment 继续执行后续操作。默认为 600 秒,如果觉得改时间太长,可以按照可接受的时间来修改配置,例如配置为 120 秒 progressDeadlineSeconds: 120

猜你喜欢

转载自blog.csdn.net/An1090239782/article/details/111169195
今日推荐