准备步骤
准备一个jenkins
我这里代码在本地机房编译,通过一台阿里云跳板机,再发布到k8s-op-m01
这里并没有采用configmap来挂载配置文件,是套用了一个统配的配置文件,通过构建前的shell脚本去实现替换。因为没有生产环境,也没有强制分离namespace
遇到的问题
1 可用pod的数量
deployment 更新的时候虽然pod显示2个都已经running,但是由于服务注册等延迟,会有一段时间eureka上没有一个可用的服务 。 这里采用了lifecycle里的postStart,让容器在启动的时候sleep 30,让pod先注册,等到30秒之后,deployment里才会变为ready这样就会有3个pod在eureka上,确保之前的deployment和新的deployment的rs在交替时,有服务可用。
2 优雅退出
在运行1个replicas的deployment的时候,明明deployment已经更换镜像,原有的pod也已经销毁,但是eureka上迟迟不更新状态。这个问题也是采用了lifecycle里的preStop,让容器在退出之前先执行一下退出java进程的命令,告诉eureka,我下线了。这样之后基本容器销毁前eureka上的注册信息已经更新掉了。
下图是在更新拥有2个replicas的镜像的时候 出现3个pod的情况,确保服务可用
其余的没啥好说。直接丢一个部署过程的截图吧
大概就是 下代码 > 编译 > 拷贝代码 > 创建Dockerfile > 打镜像并push > 部署k8s > 健康检查
pipline的脚本
node {
env.PRO_NAME = "ac"
env.PRO_TYPE = "core"
stage('Vars') {
sh "echo ${WORKSPACE} "
sh "echo ${Branch} "
sh "echo ${deploy_step} "
sh "echo ${env.JOB_NAME} "
//sh "sh /ops/java_scripts/pipline-deploy.sh ${env.PRO_NAME} ${env.PRO_TYPE} ${Branch} ${env.JOB_NAME} ${deploy_step} ${BUILD_NUMBER} ${Version} "
}
stage('Pull Coding') { // for display purposes
// Get some code from a GitHub repository
git credentialsId: '', url: 'git@gitee.com:jiaminxu/java-ac-core-course.git'
// Get the Maven tool.
// ** NOTE: This 'M3' Maven tool must be configured
// ** in the global configuration.
}
stage('Maven Build') {
//sh "mvn -pl ${MODULE} -am clean package"
sh "echo Maven Build"
sh "sh /ops/java_scripts/pipline-deploy.sh ${env.PRO_NAME} ${env.PRO_TYPE} ${Branch} ${env.JOB_NAME} ${deploy_step} ${BUILD_NUMBER} ${Version} func_compile "
}
stage('Scp Jar') {
sh "echo build image"
sh "sh /ops/java_scripts/pipline-deploy.sh ${env.PRO_NAME} ${env.PRO_TYPE} ${Branch} ${env.JOB_NAME} ${deploy_step} ${BUILD_NUMBER} ${Version} func_scp "
}
stage('Create Dockerfile') {
sh "echo build image"
sh "sh /ops/java_scripts/pipline-deploy.sh ${env.PRO_NAME} ${env.PRO_TYPE} ${Branch} ${env.JOB_NAME} ${deploy_step} ${BUILD_NUMBER} ${Version} func_docker "
}
stage('Image Push') {
sh "echo build image"
sh "sh /ops/java_scripts/pipline-deploy.sh ${env.PRO_NAME} ${env.PRO_TYPE} ${Branch} ${env.JOB_NAME} ${deploy_step} ${BUILD_NUMBER} ${Version} func_push "
}
stage('Deploy') {
sh "echo build image"
sh "sh /ops/java_scripts/pipline-deploy.sh ${env.PRO_NAME} ${env.PRO_TYPE} ${Branch} ${env.JOB_NAME} ${deploy_step} ${BUILD_NUMBER} ${Version} func_deploy "
}
stage('Health Check') {
//sh "/root/script/deploy.sh"
sh "echo Health Check"
sh "sh /ops/java_scripts/pipline-deploy.sh ${env.PRO_NAME} ${env.PRO_TYPE} ${Branch} ${env.JOB_NAME} ${deploy_step} ${BUILD_NUMBER} ${Version} func_health "
}
}
deployment模版
apiVersion: apps/v1
kind: Deployment
metadata:
# deployment名字 和svc 和ingress绑定没关系
name: pre-demojob-dp
namespace: default
spec:
replicas: 2
# 在定义模板的时候必须定义labels,因为Deployment.spec.selector是必须字段,而他又必须和template.labels对应
selector:
matchLabels:
app: pre-demojob
# template里面定义的内容会应用到下面所有的副本集里面(例如depolyment下的pod),在template.spec.containers里面不能定义labels标签。可以kubectl get pods --show-labels查看
template:
metadata:
labels:
app: pre-demojob
env: pre
spec:
containers:
# containers名字 和svc 和ingress绑定没关系
- name: pre-demojob
image: harbor.aircourses.com/kubernetes/demojob:vversion
#image: harbor.aircourses.com/kubernetes/wehub:v7.80
lifecycle:
# sleep 30用于确保原有的pod 不是全部马上退出 造成eureka上服务一个pod都没有
postStart:
exec:
command: ["/bin/sleep", "30"]
preStop:
exec:
command: ["/bin/sh", "-c", "sh /data/module/demojob/bin/start.sh stop"]
ports:
- name: http
containerPort: 80
imagePullSecrets:
- name: myregistrykey
部署用的shell脚本 仅做参考 也是第一次尝试 没什么经验
#!/bin/sh
# @Time : 2020-02-04
# @Author : jiaminxu
# @Description : 用于jenkins部署kubernetes
#--------------------------------------------
########## shell脚本传入的参数 ##########
########## shell脚本传入的参数 ##########
########## shell脚本传入的参数 ##########
# 项目名称
pro_name=${1} #{pd|ac|sc|..}
# 项目类型
pro_type=${2} #{api|core}
# 部署环境
pro_env=${3} #{dev|pre|master|test|test2}
# jenkins的job名称
job_name=${4} #{ac-client|ac-core-assist}
# 此次部署步骤
deploy_step=${5} #{Compile|Deploy|Build|..}
# 此次部署的非0构建号
build_number=${6} #{1|2|3}
# 需要回滚的非0构建号
rollback_version=${7} #{1|2|3}
# 此次执行的函数名称
name_func=${8}
# docker_var
docker_var=`echo ${job_name} | sed "s/.*${pro_name}-//g " | sed "s/.*${pro_type}-//g"`
# cut_job_name
cut_job_name=`echo ${job_name} | sed "s/.*k8s-//g "`
echo "$pro_name $pro_type $pro_env $job_name $deploy_step $build_number $rollback_version $docker_var $cut_job_name"
jenkins_result_file="/root/.jenkins/workspace/${job_name}/jenkins_result_${job_name}.txt"
deploy_ip="192.168.12.21"
# 定义远程部署文件夹 即Dockerfile文件所在的路径
module_dir=/data/module/kubernetes/${pro_env}/${pro_name}/${pro_type}/${job_name}
echo "[tags] module_dir: $module_dir"
# 定义上传jar包的远端文件夹 即Dockerfile编译的lib包路径
target_dir="${module_dir}/lib"
version=${build_number}
func_user()
{
#--------------------------------------------
########## 判断执行的用户是否有权限执行生产 ##########
########## 判断执行的用户是否有权限执行生产 ##########
########## 判断执行的用户是否有权限执行生产 ##########
# jenkins_result_file
jenkins_result_file="/root/.jenkins/workspace/${job_name}/jenkins_result_${job_name}.txt"
echo "[tags] jenkins_result_file :$jenkins_result_file"
echo $jenkins_result_file #用于判断部署脚本是否异常退出 1为异常,如果启动成功会在脚本最后输入0
# 用于判断部署脚本是否异常退出 1为异常,如果启动成功会在脚本最后输入0
echo 1 >$jenkins_result_file
echo "[tags] 判断执行用户权限"
# 只有admin 和 jiaminxu 或者api调用时用户名为空 可以部署master
if [ "$user" != "admin" ] && [ "$user" != "jiaminxu" ] && [ "$user" != "" ] && [ "$pro_env" == "master" ]; then
echo "[ERROR] 当前用户:${user},没有构建生产项目权限 !!"
echo 1 >$jenkins_result_file
exit 1
fi
echo [tags] 当前用户:${user}
}
func_base()
{
#--------------------------------------------
########## 判断 [ac|pd|sc|hp] 参数内容正确性 ##########
########## 判断 [ac|pd|sc|hp] 参数内容正确性 ##########
########## 判断 [ac|pd|sc|hp] 参数内容正确性 ##########
if [[ ${pro_name} != "ac" ]] && [[ ${pro_name} != "pd" ]] && [[ ${pro_name} != "sc" ]] && [[ ${pro_name} != "hp" ]]; then
echo "[ERROR] 请输入正确的项目参数 [pd,ac,sc,hp]: 当前是:"${pro_name}
echo 1 >$jenkins_result_file
exit 1
fi
}
func_compile()
{
#--------------------------------------------
########## 编译 ##########
########## 编译 ##########
########## 编译 ##########
# 申明jenkins WORKSPACE
module_path="/root/.jenkins/workspace/${job_name}"
echo build_number: $build_number rollback_version: $rollback_version # 暂时没有设计回滚功能
echo 1 >$jenkins_result_file
#--------------------------------------------
########## 进入WORKSPACE编译 ##########
########## 进入WORKSPACE编译 ##########
########## 进入WORKSPACE编译 ##########
if [ "$pro_env" == "master" -a "$deploy_step" == "Deploy" ] || [ "$pro_env" == "master" -a "$deploy_step" == "Rollback" ] || [ "$pro_env" == "master" -a "$deploy_step" == "Restart" ]; then # 暂时没有设计好需要哪些功能 暂定部署 伸缩 [重启|回滚]? 非生产不允许伸缩 回滚可能用得上这个判断
echo "[tags] 生产直接部署 不进行mvn编译"
else
echo "[tags] 执行clean_deploy"
cd ${module_path}
echo "[tags] module_path:${module_path}"
return_value=$(mvn clean install -N && mvn clean deploy)
# 输出编译过程
echo -e "[tags] 编译输出:\n${return_value}"
# 判断是否出现编译错误
num=$(echo "${return_value}" | grep "ERROR" | wc -l)
[ $num -ne 0 ] && {
date +'%Y-%m-%d %H:%M:%S'
echo "[ERROR] 编译报错"
echo 1 >$jenkins_result_file
exit 7
}
echo "[tags] 删除spring-boot-devtools-*.jar 避免出现因为远程调试造成的性能问题"
echo "[tags] rm -rf ${module_path}/target/lib/spring-boot-devtools-*.jar"
rm -rf ${module_path}/target/lib/spring-boot-devtools-*.jar
# 输出版本号
version=$(echo -e "编译输出:\n${return_value}" | grep "[INFO]" | grep "Building" | grep ${cut_job_name} | grep -v jar | head -n 1 | awk -F" " '{print $4}')
echo "[tags] version : $version"
echo ${version} > /ops/java_scripts/deploy_version/$Branch_$JOB_NAME.txt
echo "echo ${version} > /ops/java_scripts/deploy_version/$Branch_$JOB_NAME.txt"
fi
}
func_scp()
{
#--------------------------------------------
########## 编译完成,上传至跳板服务器,进行编译 ##########
########## 编译完成,上传至跳板服务器,进行编译 ##########
########## 编译完成,上传至跳板服务器,进行编译 ##########
# 判断是不是core服务 core服务和api服务编译生产的lib包位置不太一样
if [ ${pro_type} == "core" ]; then
jar_path="/root/.jenkins/workspace/${job_name}/boot"
else
jar_path="/root/.jenkins/workspace/${job_name}"
fi
echo "[tags] ---------------------------------------------------------------------"
echo "[tags] 本地项目存放路径 --> "${module_path}
echo "[tags] 本地Jar包存放路径--> "${jar_path}
echo "[tags] ---------------------------------------------------------------------"
# 定义远程部署文件夹 即Dockerfile文件所在的路径
module_dir=/data/module/kubernetes/${pro_env}/${pro_name}/${pro_type}/${job_name}
echo "[tags] module_dir: $module_dir"
# 定义上传jar包的远端文件夹 即Dockerfile编译的lib包路径
target_dir="${module_dir}/lib"
echo "[INFO] ----------------------< DIR >----------------------"
echo "[INFO] kubernetes路径 ----> "${module_dir}
echo "[INFO] Jar包存放路径 ----> "${target_dir}
# 部署跳板机的ip 用于编译镜像 打tag上传 本地带宽限制 可能导致上传太慢
deploy_ip="192.168.12.21"
echo "[tags] 部署地址为${deploy_ip}"
ssh root@${deploy_ip} "\
[ ! -d ${module_dir} ]&& mkdir -p ${module_dir};\
rm -rf ${module_dir}/* && mkdir -p ${module_dir}/lib"
echo "[tags] 拷贝代码至${deploy_ip}的${module_dir}下"
scp ${jar_path}/target/*.jar root@${deploy_ip}:${module_dir}/lib
echo "[tags] scp ${jar_path}/target/*.jar root@${deploy_ip}:${module_dir}/lib"
scp ${jar_path}/target/lib/*.jar root@${deploy_ip}:${module_dir}/lib
echo "[tags] scp ${jar_path}/target/lib/*.jar root@${deploy_ip}:${module_dir}/lib"
#--------------------------------------------
########## 复制指定环境指定类型的模版文件夹bin和conf到部署文件夹中 ##########
########## 复制指定环境指定类型的模版文件夹bin和conf到部署文件夹中 ##########
########## 复制指定环境指定类型的模版文件夹bin和conf到部署文件夹中 ##########
# 拷贝对应的demo配置文件
ssh root@${deploy_ip} "cp -r /data/module/kubernetes/demo/${Branch}_${pro_type}_demo/bin ${module_dir}/"
ssh root@${deploy_ip} "cp -r /data/module/kubernetes/demo/${Branch}_${pro_type}_demo/conf ${module_dir}/"
ssh root@${deploy_ip} "ls -l ${module_dir}"
ssh root@${deploy_ip} "ls -l ${module_dir}/conf"
# 替换demo里的关键字
ssh root@${deploy_ip} "cd ${module_dir};for i in bin/start.sh conf/application.properties conf/logback.xml ;do sed -i 's/demojob/$docker_var/g' \$i;done"
echo ssh root@${deploy_ip} "cd ${module_dir};for i in bin/start.sh conf/application.properties conf/logback.xml ;do sed -i 's/demojob/$docker_var/g' \$i;done"
}
func_docker()
{
#--------------------------------------------
########## 生成Dockerfile到部署文件夹中 ##########
########## 生成Dockerfile到部署文件夹中 ##########
########## 生成Dockerfile到部署文件夹中 ##########
ssh root@${deploy_ip} "echo FROM harbor.aircourses.com/kubernetes/jdk-1.8.0_161:v1 > ${module_dir}/Dockerfile "
ssh root@${deploy_ip} "echo 'MAINTAINER PDABC Enterprise Container Images <hugo.xu@pdabc.com>' >> ${module_dir}/Dockerfile"
ssh root@${deploy_ip} "echo COPY lib /data/module/${cut_job_name}/lib >> ${module_dir}/Dockerfile"
ssh root@${deploy_ip} "echo COPY bin /data/module/${cut_job_name}/bin >> ${module_dir}/Dockerfile"
ssh root@${deploy_ip} "echo COPY conf /data/module/${cut_job_name}/conf >> ${module_dir}/Dockerfile"
ssh root@${deploy_ip} "echo ENTRYPOINT [\\\"sh\\\", \\\"/data/module/${cut_job_name}/bin/start.sh\\\", \\\"start\\\"] >> ${module_dir}/Dockerfile"
}
func_push()
{
#--------------------------------------------
########## docker build 生成镜像 并上传harbor ##########
########## docker build 生成镜像 并上传harbor ##########
########## docker build 生成镜像 并上传harbor ##########
version=${build_number}
ssh root@${deploy_ip} " cd ${module_dir};docker build -t harbor.aircourses.com/kubernetes/${cut_job_name}:v${version} . "
ssh root@${deploy_ip} " docker push harbor.aircourses.com/kubernetes/${cut_job_name}:v${version}"
}
func_deploy()
{
#--------------------------------------------
########## 更新deployment ##########
########## 更新deployment ##########
########## 更新deployment ##########
echo cp /data/module/kubernetes/demo/deployment/${Branch}/deployment.yaml ${module_dir}/deployment.yaml
ssh root@${deploy_ip} " yes|cp /data/module/kubernetes/demo/deployment/${Branch}/deployment.yaml ${module_dir}/deployment.yaml "
ssh root@${deploy_ip} " sed -i \"s/demojob/${cut_job_name}/g\" ${module_dir}/deployment.yaml "
ssh root@${deploy_ip} " sed -i \"s/version/${version}/g\" ${module_dir}/deployment.yaml "
ssh root@${deploy_ip} " sed -i \"s/version/${version}/g\" ${module_dir}/deployment.yaml "
echo "[tags] 输出即将部署的deployment.yaml"
echo "[tags] 输出即将部署的deployment.yaml"
echo "[tags] 输出即将部署的deployment.yaml"
ssh root@${deploy_ip} " cat ${module_dir}/deployment.yaml "
ssh root@${deploy_ip} " ssh root@k8s-op-m01 \"[ ! -d ${module_dir} ]&& mkdir -p ${module_dir};rm -rf ${module_dir}/* \" "
echo "[tags] mkdir -p ${module_dir}"
ssh root@${deploy_ip} " scp ${module_dir}/deployment.yaml k8s-op-m01:${module_dir}/ "
echo "[tags] kubectl apply -f ${module_dir}/deployment.yaml"
ssh root@${deploy_ip} " ssh root@k8s-op-m01 \"kubectl apply -f ${module_dir}/deployment.yaml \""
}
func_health()
{
#--------------------------------------------
########## 健康检查deployment ##########
########## 健康检查deployment ##########
########## 健康检查deployment ##########
success=0
count=60
name="$Branch-$cut_job_name-dp"
# fen ge de shi hou zi dong sheng cheng su zhu
IFS=","
sleep 5
while [ ${count} -gt 0 ]
do
replicas=$(ssh 192.168.12.21 "ssh 192.168.12.58 \"kubectl get deploy $name -o go-template='{{.status.replicas}},{{.status.updatedReplicas}},{{.status.readyReplicas}},{{.status.availableReplicas}}'\"")
echo "replicas: ${replicas}"
arr=(${replicas})
echo status.replicas: ${arr[0]}
echo status.updatedReplicas: ${arr[1]}
echo status.readyReplicas: ${arr[2]}
echo status.availableReplicas: ${arr[3]}
if [ "${arr[0]}" == "${arr[1]}" -a "${arr[1]}" == "${arr[2]}" -a "${arr[2]}" == "${arr[3]}" ];then
echo "health check success!"
success=1
break
fi
((count--))
sleep 2
done
if [ ${success} -ne 1 ];then
echo "health check failed!"
exit 1
fi
echo "count剩余${count}"
echo 0 >$jenkins_result_file # 这里暂时写0 否则jenkins构建完会显示失败
}
# 部署kubernetes进程的默认用户
# deploy_user="root"
${name_func}