一. 环境准备
本文示例如何使用 Jenkins 自动编译打包, 构建 Docker 镜像, 提交到本地 Docker Registry 镜像服务, 并通过 Helm 发布到 K8S 集群.
整体的流程图如下:
本文涉及到的环境:
-
服务端
ttg12
:- OS: Ubuntu Server 18.04
- IP: 192.168.31.12
-
应用:
- Docker v19.03.6
- Docker Registry v2.6.1
- K8S kubectl v1.18.5.
- Jenkins Docker v2.235.1-lts
-
本地工作电脑
- OS: MacOS Catalina 10.15.5
- IP: 192.168.31.22
-
应用:
- K8S kubectl 工具, 连接到 k8s master
二. 安装和配置相关应用
2.1 Docker 准备
在 ttg12
上安装好 docker, 并配置好国内镜像.
在 ttg12
上通过 docker 安装本地 Docker Registry (v2.6.1). 参考私有化部署极简 Docker Registry, 并将自签名证书复制到 /etc/docker/certs.d/
.
2.2 K8S 准备
准备一个 K8S 集群或者单节点 K8S, 在 ttg12
上安装好 kubectl 工具, 并配置好 /etc/.kube/config
.
在 ttg12
上安装好 helm 3 客户端. Linux 下 Helm 3 的安装很简单, 直接下载二进制文件下来, 解压, 移动到/usr/local/bin/
目录下即可.
# 这里以 helm v3.3.0 为例
curl https://get.helm.sh/helm-v3.3.0-rc.1-linux-amd64.tar.gz > /tmp/helm-v3.3.0-rc.1-linux-amd64.tar.gz
tar zxf /tmp/helm-v3.3.0-rc.1-linux-amd64.tar.gz -C /tmp/helm-v3.3.0-rc.1-linux-amd64/helm `/usr/local/bin/helm`
sudo cp /tmp/helm-v3.3.0-rc.1-linux-amd64/helm /usr/local/bin/helm
sudo rm -rf /tmp/helm-v3.3.0-rc.1-linux-amd64.tar.gz /tmp/helm-v3.3.0-rc.1-linux-amd64/helm
2.3 安装 Jenkins
本文使用 Docker 方式安装 Jenkins. 具体步骤参考本文另外一篇博文 Docker安装Jenkins并支持Maven,Docker,Helm.
启动容器后, 登录 Jenkins, 安装插件 Docker plugin
and Docker Pipeline
. 否则会报异常:
Obtained Jenkinsfile from git [https://gitee.com/facelessdemos/SpringBootDemo.git](https://gitee.com/facelessdemos/SpringBootDemo.git)
Running in Durability level: MAX\_SURVIVABILITY
org.codehaus.groovy.control.MultipleCompilationErrorsExcept.ion: startup failed:
WorkflowScript: 34: Invalid agent type "docker" specified. Must be one of \[any, label, none\] @ line 34, column 17.
docker {
^
参考 Gitee 官方文档, 安装 Gitee
插件.
三. 代码准备
在项目根目录下新建 Dockerfile, Jenkinsfile 文件 以及 helm 目录.
3.1 Dockerfile
FROM frolvlad/alpine-java:jdk8-slim
#在build镜像时可以通过 --build-args profile=xxx 进行修改
ARG profile
ENV SPRING_PROFILES_ACTIVE=${profile}
#项目的端口
EXPOSE 8080
WORKDIR /mnt
#修改时区
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories \
&& apk add --no-cache tzdata \
&& ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime \
&& echo "Asia/Shanghai" > /etc/timezone \
&& apk del tzdata \
&& rm -rf /var/cache/apk/* /tmp/* /var/tmp/* $HOME/.cache
COPY ./web/target/web-1.0-SNAPSHOT.jar ./app.jar
ENTRYPOINT ["java", "-jar", "/mnt/app.jar"]
3.2 Jenkinsfile
image_tag = "1.0.0-snapshot" //定一个全局变量,存储Docker镜像的tag(版本)
pipeline {
agent any
environment {
//GIT_REPO = "${env.gitlabSourceRepoName}" //从Jenkins Gitlab插件中获取Git项目的名称
GIT_REPO = "springbootdemo"
GIT_BRANCH = "${env.gitlabTargetBranch}" //项目的分支
GIT_TAG = sh(returnStdout: true,script: 'git describe --tags --always').trim() //commit id或tag名称
KUBE_CONFIG_LOCAL = credentials('local-k8s-kube-config') //开发测试环境的kube凭证
KUBE_CONFIG_PROD = "" //credentials('prod-k8s-kube-config') //生产环境的kube凭证
DOCKER_REGISTRY = "registry.faceless.com:5443" //Docker仓库地址
DOCKER_NAMESPACE = "facelessdemo" //命名空间
DOCKER_IMAGE = "${DOCKER_REGISTRY}/${DOCKER_NAMESPACE}/${GIT_REPO}" //Docker镜像地址
INGRESS_HOST_DEV = "dev.springbootdemo.cn" //开发环境的域名
INGRESS_HOST_TEST = "test.springbootdemo.cn" //测试环境的域名
INGRESS_HOST_PROD = "prod.springbootdemo.cn" //生产环境的域名
K8S_NAMESPACE = "demo-dev"
}
parameters {
string(name: 'ingress_path', defaultValue: '/', description: '服务上下文路径')
string(name: 'replica_count', defaultValue: '2', description: '容器副本数量')
}
stages {
stage('Code Analyze') {
agent any
steps {
echo "1. 代码静态检查"
}
}
stage('Maven Build') {
agent any
steps {
echo "2. 代码编译打包"
sh 'mvn clean package -Dfile.encoding=UTF-8 -Dmaven.test.skip=true'
}
}
stage('Docker Build') {
agent any
steps {
echo "3. 构建Docker镜像"
echo "镜像地址: ${DOCKER_IMAGE}"
script {
def profile = "dev"
image_tag = "dev." + env.GIT_TAG
if (env.gitlabTargetBranch == "develop") {
image_tag = "dev." + env.GIT_TAG
profile = "dev"
} else if (env.gitlabTargetBranch == "pre-release") {
image_tag = "test." + env.GIT_TAG
profile = "test"
} else if (env.gitlabTargetBranch == "master"){
// master分支则直接使用Tag
image_tag = env.GIT_TAG
profile = "prod"
}
//通过--build-arg将profile进行设置,以区分不同环境进行镜像构建
sh "docker build --build-arg profile=${profile} -t ${DOCKER_IMAGE}:${image_tag} ."
sh "docker push ${DOCKER_IMAGE}:${image_tag}"
sh "docker rmi ${DOCKER_IMAGE}:${image_tag}"
}
}
}
stage('Helm Deploy') {
agent any
steps {
echo "4. 部署到K8s"
sh "helm upgrade -i --namespace=${K8S_NAMESPACE} --set image.repository=${DOCKER_IMAGE} --set image.tag=${image_tag} ${GIT_REPO} ./helm/"
}
}
}
}
3.3 Helm配置
通过helm 客户端创建对应版本的 chart 配置文件清单:
helm create springbootdemo
得到相应的文件清单如下:
1) 我们需要修改其中的 values.yaml
文件. 注意以下带注释的部分.
# 启动容器副本数
replicaCount: 2
image:
# 项目打包出来的 docker image 提交在本地镜像仓库中
repository: registry.faceless.com:5443/facelessdemo/springbootdemo
pullPolicy: IfNotPresent
# 默认image标签. 需在Jenkinsfile中的 `sh helm upgrade` 命令中覆盖.
tag: "dev.latest"
imagePullSecrets: []
# 项目名
nameOverride: "springbootdemo"
fullnameOverride: ""
# 容器的端口暴露及环境变量配置
container:
port: 8080
env: []
serviceAccount:
create: true
annotations: {}
name: ""
podAnnotations: {}
podSecurityContextext: {}
securityContext: {}
# 本示例通过Ingress提供对外服务, 所以Service使用ClusterIP模式即可
service:
type: ClusterIP
port: 8080
# Ingress配置域名和路径. 测试时需要将`dev.springbootdemo.cn`域名指向k8s的任一台worker的IP.
ingress:
enabled: true
annotations: {}
hosts:
- host: dev.springbootdemo.cn
paths: [/]
tls: []
resources: {}
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 4
nodeSelector: {}
tolerations: []
affinity: {}
# Probe延时参数
probe:
livenessDelaySeconds: 120
readinessDelaySeconds: 120
2) templates/depolyment.yaml 文件. templates
文件夹下的文件是 K8S deploymnet, service, ingress 等通用配置模板, 一般情况下无需修改. 但是对于 SrpingBoot 项目, 特别是涉及到启动过程中需要连接数据库/消息队列/其他第三方服务的情况, 默认在启动后就进行服务探测(Probe). 而此时可能 tomcat 还没起来, 所以容易误报为服务失败, 导致无端重启容器. 因此我们需要修改 templates/depolyment.yaml
文件, 将其中 Probe 延时改为一个相对比较合理的时间.
我们修改 templates/depolyment.yaml
文件, 在 .spec.template.spec.containers
节点下增加 .livenessProbe
和 .readinessProbe
两个节点. 其他部分没有任何改动, 所以这里只给出这一段代码片段:
containers:
- name: {{ .Chart.Name }}
# 省略...
ports:
- name: http
containerPort: {{ .Values.container.port }}
protocol: TCP
livenessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: {{ .Values.probe.livenessDelaySeconds }}
readinessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: {{ .Values.probe.readinessDelaySeconds }}
四. 配置 Jenkins
1) 新建一个 Item, 取一个名字 (比如: SpringBootDemo-K8S), 选择 Pipeline 类型.
2) 进入到配置页面, General -> Description
字段输入项目描述.
3) 如果你的 Jenkins 可以外网访问, 或者跟你的 Gitlab 处于同一局域网, 可以由 Gitee 或者 Gitlab 触发 WebHook, 那么在 Build Triggers
模块进行相应的配置. 本示例因为 Gitee 无法触达内网部署的 Jenkins, 所以没有配置 Build Triggers
. 后面需要手动触发编译流程.
4) Pipeline的定义如下图所示 (注意, 我这里取的是base
分支, 如果你需要取master
分支, 将下图中的3处base
改为master
:
最后, 手动触发编译, Pipeline 的 Stage View 如下图所示: