K8S基于Jenkins的自动化部署
问题
问题分为两种:
方法论的问题:比如团队采用主干开发,主干发布的模式,但是质量得不到保证,这时通过分析讨论决定采用采用主干开发,分支发布的模式来解决,这属于从方法论层面解决问题。
落地执行的问题:已经知道应该采用主干开发,分支发布的模式,但在实际操作的时候,难以执行下去,这属于执行的问题。
在《不断进化的分支和需求管理》一文的最后提到会引入 release 分支和 tag,实际也这么做了,但效果并不理想,原因是执行的不严格,没有做到位,具体原因如下:
- 发布时是对分支进行构建发布,发布后再在 GitLab 中打上 tag,一忙起来很容易忘记;
- 镜像的版本也是如此。
解决思路
目的其实很简单,就是让代码的 tag 和镜像的 tag 能够一致,靠人工去做这些事情比想象的要更加困难,所以稍微转换了下思路就能实现自动化,也就可以解决这个问题。
-
之前提到的 release 分支只做最终的集成测试;
-
需要发布时就从 release 分支创建 tag,对 tag 来做发布,通过脚本自动创建镜像 tag 进行 push 。
实际操作
原来在 jenkins 中对分支进行发布,需要设置特定的分支,现在需要对 tag 进行发布,tag 是不断进行创建的,就需要用到 jenkisn 的参数化功能。
jenkins 的参数化需要用到 Git Parameter 插件,可以在 jenkins 的插件管理界面中直接安
装,如果安装失败,可以在这个地址进行下载:http://mirror.xmission.com/jenkins/plugins/git-parameter/latest/,更多插件的使用说明参考官网:https://plugins.jenkins.io/git-parameter/
具体配置步骤如下:
安装Git Parameter
然后在可选插件中搜索
Git Parameter
,我这里已经安装了,安装完成后选择自动重启Jenkins。配置完成后,我们新建个发布任务,任务类型选择参数化构建过程,
然后在参数化构建过程中配置参数tag,可以添加相应的描述信息。
在源码配置中,由之前的master
,调整成${tag}
;如图:
这里指定分支,改为${tag};
返回我们的项目,可以看到由之前的立即构建变为Build With Parameters
。
按照 tag 进行构建搞定后,剩下就是需要在构建脚本中获取到最新的 tag 名称,并作为参数设置到容器的环境变量和镜像的 tag 中:
-
首先进入到 jenkins 配置的程序目录,使用
git describe --abbrev=0 --tags
获取 tab 名称; -
前端容器使用环境变量的方式将 tag 名称传入,并最终在界面显示;
-
容器镜像使用参数的方式拼接上 tag 名称。
#!/bin/bash TAGNAME=`git describe --abbrev=0 --tags` echo "tag name is:" $TAGNAME HARBOR=harbor.juxingta.com SERVER=jdd-user-center DOCKER_TAG=$HARBOR/library/$SERVER:$TAGNAME cp -R /home/hongjie/jenkins/images/agent jdd-user-center/ docker build -t $SERVER -f DockerFile . docker tag $SERVER $DOCKER_TAG docker push $DOCKER_TAG
我们点击Build with Parameters
,需要选择gitlab代码中打的tag标签信息。然后点击开始构建。
构建完成后,可以将docker镜像推送到我们自己的Harbor仓库中。
K8S-CIDC
上面文章是基于普通方式,这里我们使用jenkins一键部署到k8s,构建系统的CIDC,这里主要使用jenkins的Pipeline方式进行构建,主要步骤分为以下步骤:
- 拉取代码
- build代码
- 打包成镜像并推送至harbor
- 发布deply
拉取代码
这里我们从代码仓库git获取代码,我们新建立一个流水线任务jdd-k8s-user,这里以user服务为例子进行讲解。
首先创建个任务
然后配置pull代码任务,这里需要注意的是,如果代码仓库是私有的需要配置下credentialsId
。
配置完成后,我们验证下能否成功获取代码。
我们可以查看构建日志,也可以从阶段时图中查看相关信息状态。
build代码
pull代码后,我们Build代码,我们继续配置下一阶段的任务。我们继续完善构建脚本,新增一下信息后重新构建
stage('Maven Build') {
sh 'mvn -am clean package'
}
可以看到,Maven Build构建成功,这里我们已经将代码打成了jar包,接下来我们构建docker镜像。
打包成镜像并推送至harbor
我们继续将jar包打成docker Image,这里我们编写一个build_image.sh的脚本,我们在jenkins中执行该脚本,进行镜像构建。
首先我们配置下jenkins,执行build_image.sh脚本。
stage('Build DockerImahe') {
sh '/var/jenkins/script/build-image.sh'
}
在/var/jenkins/script/
目录下创建build-image.sh
脚本。脚本内容如下:
#!/bin/bash
# 判断构建目录是否存在
if ["${BUILD_DIR}" == ""];then
echo "env 'BUILD_DIR' is not set"
exit 1
fi
DOCKER_DIR=${BUILD_DIR}/${JOB_NAME}
# 获取agent
if [ ! -d ${AGENT_DIR}];then
echo "env 'AGENT_DIR' is not set"
exit 1
fi
# 如果docker目录不存在则创建
if [ ! -d ${DOCKER_DIR} ];then
mkdir -p ${DOCKER_DIR}
fi
echo "docker workspace: ${DOCKER_DIR}"
# 获取编译模块
JENKINS_DIR=${WORKSPACE}/${MODULE}
echo "jenkins workspace: ${JENKINS_DIR}"
# p判断jar是否存在
if [ ! -f ${JENKINS_DIR}/target/${MODULE}.jar ];then
echo "target jar file not found ${JENKINS_DIR}/target/${MODULE}.jar"
exit 1
fi
cd ${DOCKER_DIR}
pwd
rm -fr *
cp ${AGENT_DIR}/* ${DOCKER_DIR}
cp ${JENKINS_DIR}/target/${MODULE}.jar .
mv ${JENKINS_DIR}/DockerFile .
VERSION=$(date +%Y%m%d%H%M%S)
IMAGE_NAME=harbor.juxingta.com/jdd/${JOB_NAME}:${VERSION}
# 将镜像名称写入本地文件,供下个脚本使用
echo "${IMAGE_NAME}" > ${WORKSPACE}/IMAGE
echo "docker build image name is : ${IMAGE_NAME}"
# 构建docker镜像
docker build -t ${IMAGE_NAME} -f DockerFile .
echo "docker build success !"
docker push ${IMAGE_NAME}
echo "docker push success !"
说明:脚本中涉及的环境变量如下:{AGENT_DIR}、${MODULE},以上环境变量为了构建脚本灵活,这里在构建pipline中进行配置,现在配置如下:
node("118.195.194.75-k8s"){# 这里指定构建的节点
env.BUILD_DIR = "/var/jenkins/build-workspace"
env.MODULE = "jdd-user-center"
env.AGENT_DIR = "/var/jenkins/agent"
# 过去代码
stage('Pull') {
// Get some code from a GitHub repository
git credentialsId: 'xxxx', url: 'https://xxxx/jdd_platform.git'
}
# build代码
stage('Maven Build') {
// Get some code from a GitHub repository
sh 'mvn -am clean package'
}
# 构建docker镜像并推送到harbor
stage('Build DockerImahe') {
sh '/var/jenkins/script/build-image.sh'
}
}
我们可以看到构建镜像已经成功了,我们去我们的镜像仓库中看下镜像是否已经推送到仓库中。
可以看到,镜像已经推送到镜像仓库中,黑暗即将过去,马上看到胜利的曙光。我们进行下一步操作,将镜像更新到k8s中。
发布deply
这里发布依然采用脚本方式进行发布,我们在/var/jenkins/script/
目录下创建delpy.sh
脚本。
我们安装自己的需求编写common.yaml文件,这里我安装自己的产品需求编写的,大家可以参考下
# deplay
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{name}}
spec:
selector:
matchLabels:
app: {{name}}
replicas: 2
template:
metadata:
labels:
app: {{name}}
spec:
imagePullSecrets:
- name: jdd-harbor
containers:
- name: {{name}}
imagePullPolicy: Always
image: {{name}}
resources:
requests:
memory: 2048Mi
cpu: 1000m
limits:
memory: 2048Mi
cpu: 1000m
ports:
- name: http-port
containerPort: 9001
env:
- name: NACOS_HOST_URL
value: "http://nacos-headless"
- name: NACOS_HOST_PORT
value: "8848"
volumeMounts:
- name: logback-config-volume
mountPath: /etc/jdd
volumes:
- name: logback-config-volume
configMap:
name: {{logbackConfig}}
---
#service
apiVersion: v1
kind: Service
metadata:
name: {{name}}
spec:
ports:
- port: 80
protocol: TCP
targetPort: 8080
selector:
app: {{name}}
type: ClusterIP
# ConfigMap
---
apiVersion: v1
kind: ConfigMap
metadata:
name: {{logbackConfig}}
data:
logback-spring.xml: |
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<include resource="org/springframework/boot/logging/logback/defaults.xml"/>
<property name="log_pattern" value="%-30thread %d{yyyyMMdd HH:mm:ss.SSS} %-5level traceId=[%X{traceId}] %logger{140} - %msg%n"/>
<springProperty name="log_path" source="jdd.log.path" scope="context"/>
<property name="app_name" value="{{appName}}"/>
<property name="log_file" value="${log_path}/${app_name}"/>
<contextName>${app_name}</contextName>
<!--console控制台日志输出-->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<pattern>${log_pattern}</pattern>
</layout>
</appender>
<!--文件日志输出-->
<appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${log_file}-%d{yyyyMMdd}.log</fileNamePattern>
<maxHistory>7</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log_pattern}</pattern>
</encoder>
</appender>
<logger name="org.hibernate" level="INFO"/>
<logger name="org.springframework" level="INFO"/>
<logger name="com.netflix" level="WARN"/>
<logger name="org.apache.http" level="INFO"/>
<logger name="com.alibaba.nacos" level="INFO"/>
<logger name="jdbc.connection" level="ERROR"/>
<logger name="jdbc.resultset" level="ERROR"/>
<logger name="jdbc.resultsettable" level="INFO"/>
<logger name="jdbc.audit" level="ERROR"/>
<logger name="jdbc.sqltiming" level="ERROR"/>
<logger name="jdbc.sqlonly" level="INFO"/>
<springProfile name="dev">
<logger name="org.hibernate.SQL" level="DEBUG"/>
<logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE"/>
<root level="debug">
<appender-ref ref="console"/>
<appender-ref ref="file"/>
</root>
</springProfile>
<springProfile name="test">
<logger name="org.hibernate.SQL" level="DEBUG"/>
<logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE"/>
<root level="debug">
<appender-ref ref="console"/>
</root>
</springProfile>
<springProfile name="prod">
<root level="error">
<appender-ref ref="file"/>
<appender-ref ref="console"/>
</root>
</springProfile>
</configuration>
说明:这里名称采用{{name}}进行占位,{ { image } } 镜像名称、{ { app-name } } 日志应用名称、{ { logback-config } }日志配置文件名称。
我们将模版上传到/var/jenkins/script/template
目录下,编写delpy.sh
。内容如下
#!/bin/bash
name=${JOB_NAME}
image=$(cat ${WORKSPACE}/IMAGE)
logbackConfig="user-abc"
appName="user-server"
echo "deploying ... name : ${name}, image : ${image}"
rm -f common.yaml
cp $(dirname "${BASH_SOURCE[0]}")/template/common.yaml .
sed -i "s,{{name}},${name},g" common.yaml
sed -i "s,{{image}},${image},g" common.yaml
sed -i "s,{{logbackConfig}},${logbackConfig},g" common.yaml
sed -i "s,{{appName}},${appName},g" common.yaml
cat common.yaml
export KUBECONFIG=$KUBECONFIG:/root/.kube/cls-e1eu4py0-config # 切换上下文
kubectl config current-context # 打印当前上下文
kubectl apply -f common.yaml # 发布
我们看到整个构建已经成功了,我们去看下k8s集群的状态,
Service服务已经启动。至此我们的CICD构建基本上完成了,还差最后的一步就是健康检查,这里就先不进行健康检查了。这部分内容后续我们在完善。