Kubernetes+Docker+Registry+Gitlab+Jenkins+Maven 自动化集成发布
1 流程结构
说明:
a.用户向Gitlab提交代码,代码中包含 Dockerfile, JenkinsFile文件。
b.Jenkins监听Gitlab代码库的推送和变更事件
c.Jenkins调用maven插件对源码进行编译打包
d.Jenkins调用docker工具根据Dockerfile文件生成镜像,并推送镜像到Docker Registry仓库
e.Jenkins通过远程连接Kubernetes集群,更新Pod的模板YAML文件,调用kubectl命令更新操作
f.Kubernetes集群的Node 节点从Docker Registry仓库,拉取镜像,滚动更新应用容器。
2 实施步骤
2.1 Kubernetes、Docker、Docker Registry, Jenkins 的安装和部署
这里不说了, 可参照本博客的其他文章;
2.2 jenkins的配置
2.2.1 安装maven, jdk
【jenkins 首页】 --> 【全局工具配置】 直接选择安装即可
2.2.2 安装插件:
Pipeline Maven Integration:使用pipeline方式编写配置脚本
Extended Choice Parameter Plug-In:参数化插件,其实这个可以不用, 预装插件也可以配置简单的参数
2.2.3 添加凭证
为了更加安全,更好的管理你的各种账号,你可以预先存入到Jenkins的凭证中,当然你也可以不配置,全部写在脚本中(如果你擅长写脚本)。我这里存了git账户,服务器的账户、docker registry的账户。
2.2.4 创建发布任务
创建一个pipeline任务
我们是开发人员,不是运维人员,所以我们更加擅长写脚本,不太耐心去研究jenkins每个插件的功能。 所以这里其他的东西都不设置,只设置流水线, 参数什么的全部动脚本中读取(照图设置)
说明:
Credentials 就是你git的账户和密码;你可以预先存到jenkins配置中(2.2.3 添加凭证 中设置的);
脚本路径:是以jenkins根目录为起点的相对目录。 这个按照你代码中存放的路径设置JenkinsFile的路径;
到此你就可以发布了,当然前提是你的源码库中的脚本已经写好了。下面我们来看看相关脚本
3 部署脚本
3.1 配置文件说明
一般情况下只需要两个脚本。一个是Dockfile, 一个是JenkinsFile。先来看个实例项目(eurake 服务集群)
Dockerfile:用来打包镜像的
JenkinsFile:用来控制发布流程的。本应用分为四步,具体代码中讲解。
eureka.sh:是一些业务脚本,如果业务简单的话,可以直接写在Dockfile中的。(非必需)
eureka.yaml:kubernetes中的配置文件,第一次部署的时候会用到。(非必需)
3.2 相关脚本详解
3.2.1 Dockerfile:本应用中是将maven编译出来的jar包,和jdk环境一起打包成镜像文件。 当然这里也定义了一些环境变量和启动方式等。
FROM openjdk:8-jdk-alpine
MAINTAINER "shiguanghui <[email protected]>"
LABEL description="Eureka Image"
ADD eureka.sh /eureka.sh
ADD app-eureka.jar /eureka.jar
ENV TZ 'Asia/Shanghai'
EXPOSE 8761
RUN chmod a+x /eureka.sh
ENTRYPOINT ["/bin/sh","-c","/eureka.sh"]
主要就是继承openjdk:8-jdk-alpine的java环境, 然后把启动脚本文件(eureka.sh)和jar加入到容器中, 设置容器的时区,暴露端口, 设置脚本的执行权限,最后启动容器脚本。
值得注意:这里的容器环境是openjdk:8-jdk-alpine,其实是个busybox环境, 不支持/bin/bash, 只有/bin/sh。 所以eureka.sh中的shell脚本需要符合busybox的shell语法。 很多可以再bash执行的东西,是不能执行的。
3.2.2 JenkinsFile:用来控制发布流程的。
看一下jenkins的效果图:
本应用分为四步。分别get source code,maven build、docker build、 kubernetes deploy。 其中jenkinsFile中定义了后三步, 第一步在jenkins配置的
pipeline {
agent any
environment {
JENKINS_HOME = "/home/programs/jenkins/jenkins_home"
REGISTRY_HOST = "docker.shfuchen.net"
PATH_DOCKERFILE = "phoebus/phoebus-eureka/Dockerfile"
PATH_JAR = "phoebus/phoebus-eureka/target/"
PATH_SH = "phoebus/phoebus-eureka/eureka.sh"
K8S_NAMESPACE = "ns-ukr"
}
parameters {
string(name: 'DOCKER_IMAGE', defaultValue: 'eureka', description: '需要部署的项目镜像')
string(name: 'DOCKER_IMAGE_TAG', defaultValue: 'latest', description: '镜像版本')
}
stages {
stage('Maven Build') {
agent {
docker {
image 'maven:3-alpine'
args '-v ${JENKINS_HOME}/.m2:/root/.m2'
}
}
steps {
sh 'mvn clean package -Dfile.encoding=UTF-8 -DskipTests=true'
}
}
stage('Docker Build') {
agent any
steps {
withCredentials([usernamePassword(credentialsId: 'registry-account', passwordVariable: 'REGISTRY_PASSWORD', usernameVariable: 'REGISTRY_USER')]) {
sh "docker login -u ${REGISTRY_USER} -p ${REGISTRY_PASSWORD} ${REGISTRY_HOST}"
sh "cp -r ./${PATH_SH} ./${PATH_JAR}"
sh "docker build -f ./${PATH_DOCKERFILE} -t ${REGISTRY_HOST}/${params.DOCKER_IMAGE}:${params.DOCKER_IMAGE_TAG} ./${PATH_JAR}"
sh "docker push ${REGISTRY_HOST}/${params.DOCKER_IMAGE}:${params.DOCKER_IMAGE_TAG}"
sh "docker rmi ${REGISTRY_HOST}/${params.DOCKER_IMAGE}:${params.DOCKER_IMAGE_TAG}"
}
}
}
stage('Kubernetes Deploy') {
when {
allOf {
expression { params.DOCKER_IMAGE_TAG != null }
}
}
agent any
steps {
script{
def result = sh(script: "kubectl -n ns-ukr get statefulset eureka", returnStatus: true)
if ( result != 0 ) {
sh "kubectl apply -f ${PATH_INIT}"
}else {
sh "kubectl -n ${K8S_NAMESPACE} set image statefulset ${params.DOCKER_IMAGE} ${params.DOCKER_IMAGE}=${REGISTRY_HOST}/${params.DOCKER_IMAGE}:${params.DOCKER_IMAGE_TAG}"
}
}
}
}
}
}
说明:
a. 定义了环境变量和参数。 其中参数配置会在jenkins构建中直接创建的, 可以在发布的过程中修改。
b. Kubernetes Deploy 。 首先判断容器是否存在,如果不存在则创建容器(这下知道eureka.yaml是干嘛的了吧,是初始化容器使用的); 如果存在使用了滚动更新,只需要更新statefulset的image, kubenetes集群自动会做热更新。
3.2.3 eureka.yaml (业务配置,非必需)
apiVersion: v1
kind: Service
metadata:
name: eureka-service-internal
namespace: ns-ukr
labels:
app: eureka-service-internal
spec:
clusterIP: None
ports:
- port: 8761
protocol: TCP
targetPort: 8761
selector:
app: eureka
type: ClusterIP
---
apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
name: eureka
namespace: ns-ukr
spec:
selector:
matchLabels:
app: eureka
serviceName: "eureka-service-internal"
replicas: 2
updateStrategy:
type: RollingUpdate
template:
metadata:
labels:
app: eureka
spec:
terminationGracePeriodSeconds: 10
imagePullSecrets:
- name: registry-secret
containers:
- env:
- name: EUREKA_NODE_NAME
valueFrom:
fieldRef:
fieldPath: spec.nodeName
- name: EUREKA_POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: EUREKA_POD_NAMESPACE # 传入当前命名空间
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: EUREKA_POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: EUREKA_SERVICE_NAME # 因为pod 通过域名互相访问,需要使用headless 服务名称
value: eureka-service-internal
- name: EUREKA_APPLICATION_NAME
value: "eureka"
- name: EUREKA_REPLICAS
value: "2"
image: docker.shfuchen.net/eureka:latest # 这个镜像是修改后的。如何打包这个镜像,在后面会介绍
imagePullPolicy: Always
name: eureka
ports:
- containerPort: 8761
protocol: TCP
这里定义了一个service 和一个 statefulset。 这里是个特殊的应用所以使用statefulset, 也可以定义为deployment等普通的pod。
3.2.3 eureka.sh (业务配置,非必需)
#!/usr/bin/env sh
PORT=8761
SUFFIX="svc.cluster.local"
EUREKA_HOST_NAME="$EUREKA_POD_NAME.$EUREKA_SERVICE_NAME.$EUREKA_POD_NAMESPACE.$SUFFIX"
export EUREKA_HOST_NAME=$EUREKA_HOST_NAME
BOOL_REGISTER="true"
BOOL_FETCH="true"
EUREKA_URL_LIST=""
if [[ $EUREKA_REPLICAS = 1 ]]; then
BOOL_REGISTER="false"
BOOL_FETCH="false"
EUREKA_URL_LIST="http://admin:[email protected]$EUREKA_APPLICATION_NAME-0:$PORT/eureka/,"
echo $EUREKA_URL_LIST
else
BOOL_REGISTER="true"
BOOL_FETCH="true"
TEMP_X=""
idx=0
while [ $idx -lt $EUREKA_REPLICAS ]
do
# EUREKA_URL_LIST 中剔除自己
if [ "$EUREKA_POD_NAME" != "$EUREKA_APPLICATION_NAME-$idx" ]; then
TEMP_X="http://admin:[email protected]$EUREKA_APPLICATION_NAME-$idx.$EUREKA_SERVICE_NAME.$EUREKA_POD_NAMESPACE.$SUFFIX:$PORT/eureka/,"
EUREKA_URL_LIST="$EUREKA_URL_LIST$TEMP_X"
echo $EUREKA_URL_LIST
fi
let idx=$idx+1
done
fi
EUREKA_URL_LIST=${EUREKA_URL_LIST%?}
export EUREKA_URL_LIST=$EUREKA_URL_LIST
export BOOL_FETCH=$BOOL_FETCH
export BOOL_REGISTER=$BOOL_REGISTER
echo "start jar...."
java -jar /eureka.jar
这里是个高可用性的eureka集群实例,不做过多解释。 需要注意的是,这里的shell语句需要能在busybox环境中执行。
到此结束,你已经见证了一个自动化集成热部署环的搭建, 也是一个statefulset的集群应用的配置。 接下来你就可以在jenkins里面点点看效果了