KubeSphere使用心得
文章目录
目前公司的开发方式都是手工编译&部署,十分低效,最近将Web开发相关的项目都基于KubeSphere通过基于Jenkins的流水线方式实现了自动部署,在此过程中遇到了一些阻塞点,简单记录下它们的解决方案。
在KubeSphere
的Github仓库中有如下说明:
KubeSphere是什么
KubeSphere 愿景是打造一个以 Kubernetes 为内核的 云原生分布式操作系统,它的架构可以非常方便地使第三方应用与云原生生态组件进行即插即用(plug-and-play)的集成,支持云原生应用在多云与多集群的统一分发和运维管理。 KubeSphere 也是一个多租户容器平台,提供全栈的 IT 自动化运维的能力,简化企业的 DevOps 工作流
Kubernetes DevOps
提供开箱即用的基于 Jenkins 的 CI/CD,并内置自动化流水线插件,包括 Binary-to-Image (B2I) 和 Source-to-Image (S2I)
从上述描述可知KubeSphere
的两大底层支柱为Kubernetes
与Jenkins
,本文也主要基于这两部分进行记录。
流水线设计
基于部门现状以及参考网上资料,将产品部署环境划分为如下4种类型:
- dev,本地开发阶段
- sit,前后端各模块联调阶段
- test,软件功能测试阶段
- prod,正式使用阶段
虽然通过上述划分方式可有效的避免不同环境的互相干扰,但由于目前部门大部分产品都采用了微服务实现,软件架构类似下图所示,每个微服务模块都有单独的代码管理,导致若给前后端的每个功能模块在不同环境下都配置一套Jenkins流水线则实际的流水线数目会十分庞大,不便于管理和维护。
若对于每个功能模块在所有不同环境中都分别使用一套流水线,则其数量为功能模块数X环境种类数,造成流水线数目过大,使用不便同时也不利于后期的维护。
基于此,我们对Jenkins
流水线的构建和使用方式提出了如下要求:
- 一套
Gitlab
代码库对应一条Jenkins
流水线,实际上就是前后端的一个功能模块,使用时可动态选择分支 - 一套
Jenkins
流水线可以根据使用需求灵活的往dev
、sit
、test
、prod
这4套环境之一进行部署 dev
、sit
、test
、prod
对于同一套代码而言是分别部署的,即4套环境互不影响
动态参数
上图展示了Jenkins
进行软件构建的主要流程,从中可知若达到上述精简流水线条目的要求,则需要进行动态参数配置,基于项目实际情况,整理出如下场景:
- 软件版本,主要是后端基于Maven的项目需要正确的获取版本号
- 容器端口,
SpringBoot
应用程序对外暴露的端口,此部分需要在使用Dockerfile
构建时动态对外暴露1 - 容器端口,
Kubernetes
中创建容器时对外暴露的端口,主要用于程序访问 - 镜像版本,前后端程序构建镜像时,对应tag的动态设置
在Jenkins
中的脚本类型主要有shell
脚本和script
脚本2种类型,其中script是基于Grovvy
实现的,而Groovy是基于JVM
实现的,其在时使用上比shell
更灵活,故在流水线实现时对于参数设置与获取确定了一个如下的大致原则:
能通过
Groovy
脚本获取的变量与参数尽量通过Grovvy
获取,Shell
脚本只负责使用
script中定义参数
在script
中输入符合 Groovy
语法的代码即可,为了在多个steps之间实现参数共享,需要在定义参数时加上env.
前缀2,类似如下:
switch(PRODUCT_PHASE) {
case "sit":
env.NODE_PORT = 13003
env.DUBBO_PORT = 13903
break
case "test":
env.NODE_PORT = 14003
env.DUBBO_PORT = 14903
break
case "prod":
env.NODE_PORT = 15003
env.DUBBO_PORT = 15903
break
}
env.DUBBO_IP = "10.30.5.170"
script中读取参数
print env.DUBBO_IP
shell中读取参数
需要采用$参数名
(去掉env.
前缀)的方式,类似如下
docker build -f kubesphere/Dockerfile \
-t idp-data:$BUILD_TAG \
--build-arg PROJECT_VERSION=$PROJECT_VERSION \
--build-arg NODE_PORT=$NODE_PORT \
--build-arg DUBBO_PORT=$DUBBO_PORT \
--build-arg PRODUCT_PHASE=$PRODUCT_PHASE .
yaml文件中读取参数
在Kubernetes
中需要一个yaml文件来配置要生成的pod,其参数的获取也是采用$参数名
的方式
spec:
ports:
- name: http
port: $NODE_PORT
protocol: TCP
targetPort: $NODE_PORT
nodePort: $NODE_PORT
- name: dubbo
port: $DUBBO_PORT
protocol: TCP
targetPort: $DUBBO_PORT
nodePort: $DUBBO_PORT
selector:
app: lucumt-data-$PRODUCT_PHASE
sessionAffinity: None
type: NodePort
Dockerfile中读取参数
当在docker
的build阶段传递正确的参数后,在Dockerfile中需要采用${参数名}
的方式获取参数
# 基础镜像
FROM openjdk:8-jdk
# author
LABEL maintainer=luyunqiang
# 创建目录
RUN mkdir -p /home/lucumt
# 指定路径
WORKDIR /home/lucumt
ARG PRODUCT_PHASE
ARG NODE_PORT
ARG DUBBO_PORT
ENV PARAMS="--server.port=${NODE_PORT} --spring.application.name=lucumt-data --spring.profiles.active=${PRODUCT_PHASE} --dubbo.protocol.port=${DUBBO_PORT}"
RUN /bin/cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone
编译与部署
由于公司研发环境与互联网隔离,故需要在前后端分别设置可用的镜像才能确保编译正常。
前端
-
Nodejs
版本号升级:由于
KubeSphere
中的Node.js的版本落后于项目中使用的版本,故需要通过如下命令升级其版本:npm install node@16.13.1 --registry https://mirrors.xxx.com/repository/NPM/
-
Nginx
默认的端口为80,且其默认的conf文件较为简陋,为了实现nginx
的个性化配置,可以自己预先配置好一个conf文件,然后在docker构建时,将其覆盖即可FROM nginx RUN mkdir -p /usr/share/nginx/html/lucumt-web COPY dist /usr/share/nginx/html/lucumt-web # 采用自己定义的配置文件 COPY kubesphere/idp.conf /etc/nginx/conf.d/ EXPOSE 8080
后端
-
获取
Maven
版本号:mvn help:evaluate -Dexpression=project.version -q -DforceStdout
-
由于在
shell
中不方便定义全局变量,而Maven
的版本号只能在执行相关命令时获取,为了实现版本号的共享,可将两者结合起来,在shell
中利用maven
命令获取版本号,然后再由Groovy
脚本将其赋值为全局环境变量:env.PROJECT_VERSION = sh(script: 'mvn help:evaluate -Dexpression=project.version -q -DforceStdout', returnStdout: true) env.BUILD_TIME = new Date().format("yyyyMMdd-HHmmss") env.BUILD_TAG = PROJECT_VERSION + "-" + BUILD_TIME
K8S容器探活
KubeSphere
对于容器的启动、就绪和存活的探测依赖于Kubernetes
的相关实现,实际使用中KubeSphere
只需要能正常检测到就绪状态即可,现阶段项目中也只基于就绪探测来实现。
后端
SpringBoot
程序可基于Spring Boot Actuator中提供的actuator/health
进行就绪检测,相关配置如下:
containers:
- image: '$REGISTRY/$DOCKERHUB_NAMESPACE/lucumt-system:${BUILD_TAG}'
readinessProbe:
httpGet:
path: lucumt-system/actuator/health
port: $NODE_PORT
timeoutSeconds: 10
failureThreshold: 30
periodSeconds: 5
前端
由于前端没有单独的接口对外暴露,故可采用在Docker
容器中执行linux命令的方式来检测是否就绪,个人采用uname
,相关配置如下:
containers:
- image: '$REGISTRY/$DOCKERHUB_NAMESPACE/lucumt-system-web:${BUILD_TAG}'
readinessProbe:
exec:
command:
- uname
timeoutSeconds: 10
failureThreshold: 30
periodSeconds: 5
运行效果
运行效果如下,可实现动态的指定测试阶段和代码分支:
参考代码
- 前端部分:
- Dockerfile,参见lucumt-common-web.dockerfile
- depoly.yml,参见lucumt-common-web.yaml
- Jenkins流水线,参见lucumt-common-web.groovy
- Nginx配置文件,参见lucumt-common-web-nginx.conf
- 后端部分:
- Dockerfile,参见lucumt-system.dockerfile
- depoly.yml,参见lucumt-system.yaml
- Jenkins流水线,参见lucumt-system.groovy
参考: