k8s 线上安装 jenkins并结合 jenkinsfile 实现 helm 自动化部署
需求
1. 开发人员通过上传 gitlab 新分支代码,通过 jenkinsfile 结合jenkins 自动发现分支并自动化部署该分支对应的容器
2. 更新代码可以实现容器平滑更新
环境
1. k8s 1.16 高可用集群环境
2. harbor 私有仓库已搭建
3. gitlab 可以使用
4. 部署nfs server,可提供给jenkins 存储使用
部署jenkins
# 创建新名称空间
kubectl create ns myjenkins
# 准备配置文件 deployment、 svc 、ingress 、证书
1. mkdir /myjenkins/jenkins
2. deployment 准备配置yaml文件,jenkins-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: jenkins
namespace: myjenkins
spec:
replicas: 1
selector:
matchLabels:
app: jenkins
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
type: RollingUpdate
template:
metadata:
creationTimestamp: null
labels:
app: jenkins
spec:
containers:
- env:
- name: JAVA_OPTS
value: -Duser.timezone=Asia/Shanghai
image: jenkins:lts
imagePullPolicy: IfNotPresent
name: jenkins
ports:
- containerPort: 8080
name: web
protocol: TCP
- containerPort: 50000
name: agent
protocol: TCP
resources: {}
securityContext:
runAsUser: 0
volumeMounts:
- mountPath: /var/jenkins_home
name: jenkinshome
dnsPolicy: ClusterFirst
restartPolicy: Always
volumes:
- name: jenkinshome
nfs:
path: /data/upload/myjenkins
server: 172.24.119.30
3.jenkins agent 准备配置yaml 文件,这是jenkins 的 agent 配置,jenkins-agent.yaml
apiVersion: v1
kind: Service
metadata:
labels:
app: jenkins
name: jenkins-agent
namespace: myjenkins
spec:
ports:
- name: agent
port: 50000
protocol: TCP
targetPort: 50000
selector:
app: jenkins
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}
4. jenkins svc 配置yaml 文件 jenkins-svc.yaml
apiVersion: v1
kind: Service
metadata:
labels:
app: jenkins
name: jenkins
namespace: myjenkins
spec:
ports:
- name: web
port: 8080
protocol: TCP
targetPort: 8080
selector:
app: jenkins
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}
5. jenkins ingress 配置yaml 文件 jenkins-ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: nginx
name: jenkins
namespace: myjenkins
spec:
rules:
- host: myjenkins.tagtic.cn
http:
paths:
- backend:
serviceName: jenkins
servicePort: 8080
path: /
tls:
- hosts:
- myjenkins.tagtic.cn
secretName: all-tagtic.cn
status:
loadBalancer: {}
# 创建以上准备好的yaml 文件
kubectl create -f jenkins-deployment.yaml
kubectl create -f jenkins-agent.yaml
kubectl create -f jenkins-svc.yaml
kubectl create -f jenkins-ingress.yaml
#创建证书,已准备好服务器证书
kubectl create secret tls tls-secret --cert=1979891tagtic.cn.pem --key=1979891tagtic.cn.key -n myjenkins
#登陆jenkins
通过执行 kubectl logs -n myjenkins jenkins-7f89966ff9-622xm 获取jenkins 登陆密码
#访问jenkins,浏览器输入
https://myjenkins.tagtic.cn/
配置jenkins
1.按照推荐安装插件
FAQ
部署jenkins服务器出现Please wait while Jenkins is getting ready to work ...一直进不去该怎么办?
需要你进入jenkins的工作目录,打开-----hudson.model.UpdateCenter.xml将 url 中的
https://updates.jenkins.io/update-center.json
更改为https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json
是国内的清华大学的镜像地址。
2.安装完插件修改 admin 权限密码
3.jenkins 安装插件
Gitlab Gitlab API Kubernetes
4.点击系统管理--系统配置
4.1 修改用法、jenkins url 、系统管理员邮件地址(报警邮箱地址)
用法: 尽可能的使用这个节点
Jenkins URL:https://myjenkins.tagtic.cn/
系统管理员邮件地址: yunweimonitor@infinities.com
4.2.全局属性,增加环境变量键值对,这个环境变量在后续的 jenkinsfile 中会用到
键: URL_SUFFIX 值:xy.a9vg.com
4.3 Gitlab 需要填写 Connection name、Gitlab host URL、Credentials,写完 Test Connections
Connection name: duoniu glab
Gitlab host URL: https://glab.tag.cn
Credentials:Gitlab API token,这个凭据的tocken 需要在gitlab 的个人账户中生成,下面有详细生成方式。
4.4 添加Gitlab API token 凭据
点击 系统管理-->安全-->Manage Credentials-->添加凭据-凭据类型为 GitLab API token、API token 是以下说明在 gitlab 中生成的、ID 为自定义名称 glab_token
Gitlab API Token 获取方式:
登陆个人gitlab 账号-->点击个人头像--> 选择 settings-->选择 Access Tokens --> 输入 Name 和 Expries at --> 勾选api Access your API、read_user Read user information、read_registry Read Registry --> 点击“Create personal access token”,生成access token,记录下来。
4.5 新增加 Global Pipeline Libraries,会加载 groovy 脚本。
Library Name: mykubernetes-standard
Default version: master
Retrieval method
选择 Modern SCM
Git 项目仓库: https://glab.tag.cn/test/kubernetes-standard.git
凭据需要添加个人登录gitlab 的账号和密码,和上面添加的 Gitlab API Token 类型是不一样的。
4.5.1 新增加个人登陆gitlab 的凭据和 后续使用harbor 的凭据,这两个凭据类型为 Username with password ,
个人登录gitlab 需要填写登录gitlab 的用户名、密码、ID自定义为 glab_pass
harbor 需要填写登录harbor 的用户名、密码、ID 自定义为 harbor
FAQ:
1. Warning: CredentialId "glab_pass" could not be found.
id 名称为: glab_pass
因为 kubernetes-stand 有用到这个名称,或者修改 helm.groovy
FAQ:
ERROR: Could not find credentials entry with ID 'harbor'
添加harbor 的凭据
5. 新增加 邮件通知,填写完成可以写测试邮件看是否能发送成功
添加 SMTP服务器:smtp.exmail.qq.com
SMTP服务器:@infinities.com
6. 点击 cloud 下面的配置云
https://myjenkins.tagtic.cn/configureClouds/
配置前提是k8s 集群已配置宽泛的 rbac 策略
kubectl create clusterrolebinding permissive-binding \
--clusterrole=cluster-admin \
--user=admin \
--user=kubelet \
--group=system:serviceaccounts
1.第一步添加 kubernetes 名称:kubernetes
2.第二步连接 Kubernetes 地址:https://kubernetes.default.svc.cluster.local
3.第三步Kubernetes 命名空间: myjenkins (jenkins 的名称空间)
4.第四步Jenkins 地址:http://jenkins:8080
5.第五步Jenkins 通道:jenkins-agent.myjenkins:50000
6.第六步填写 jenkins pod 的 label: 键:jenkins 值:slave
7.第七步连接 Kubernetes API 的最大连接数 32
gitlab 钩子触发 jenkins 报错 403
FAQ:
1. 配置 jenkins 安全策略
系统管理-->安全-->全局安全配置-->授权策略-->勾选匿名用户具有可读权限
2. 系统管理--> 系统配置 Gitlab Enable authentication for '/project' end-point 取消前面勾选
第一种情况 gitlab 代码结构下有 Jenkinsfile
与gitlab 代码同级的包含有 Dockerfile 和 Jenkinsfile,这个项目包含有4个分支
#Jenkinsfile 文件内容
变量含义:
bn --> 获取当前分支名称
bn_replace --> 将分支名称中的 . 更换为 -
suffix --> 为 jenkins 配置的全局环境变量
def bn = "${env.BRANCH_NAME}";
def bn_replace = bn.replace(".", "-");
def suffix = "${env.URL_SUFFIX}";
stage('Deliver for development') {
if (bn.startsWith('build-')){
helm{
scmUrl="https://glab.tag/test/laravel-k8s-test.git"
project="test-helm2-${bn_replace}"
email="test@donews.com"
namespace="default"
branch="${bn}"
registry="harbor"
helm="donews/myapp"
helmArgs=""" --set service.port=80,ingress.hosts={myhelm-${bn_replace}.${suffix}} \
"""
}
}
}
stage('Deliver for testing') {
if (bn == 'dev'){
helm{
scmUrl="https://glab.tag/test/laravel-k8s-test.git"
project="test-helmdev"
email="test@donews.com"
namespace="default"
branch="dev"
registry="harbor"
helm="donews/myapp"
helmArgs=""" --set service.port=80,ingress.hosts={myhelmdev-xy.a99.com}"""
}
}
}
stage('Deploy for production') {
if (bn == 'master') {
helm{
scmUrl="https://glab.tag/test/laravel-k8s-test.git"
project="test-helmmaster"
email="test@donews.com"
namespace="default"
branch="master"
registry="harbor"
helm="donews/myapp"
helmArgs=""" --set service.port=80,ingress.hosts={myhelmmaster-xy.a99.com}"""
}
}
}
groovy 使用
jekins 系统配置中 Global Pipeline Libraries git 项目https://glab.tag.cn/test/kubernetes-standard.git ,在项目下新建立 vars 目录,vars 目录下 helm.groovy 内容
import hudson.model.Result
import hudson.model.Run
import jenkins.model.CauseOfInterruption.UserInterruption
def abortPreviousBuilds() {
Run previousBuild = currentBuild.rawBuild.getPreviousBuildInProgress()
while (previousBuild != null) {
if (previousBuild.isInProgress()) {
def executor = previousBuild.getExecutor()
if (executor != null) {
echo ">> Aborting older build #${previousBuild.number}"
executor.interrupt(Result.ABORTED, new UserInterruption(
"Aborted by newer build #${currentBuild.number}"
))
}
}
previousBuild = previousBuild.getPreviousBuildInProgress()
}
}
def call(body) {
// evaluate the body block, and collect configuration into the object
abortPreviousBuilds()
def pipelineParams= [:]
def reg_prefix = ""
def label = "worker-${pipelineParams.project}"
body.resolveStrategy = Closure.DELEGATE_FIRST
body.delegate = pipelineParams
body()
if (!pipelineParams.branch){
pipelineParams.branch = "master"
}
if (!pipelineParams.deployment){
pipelineParams.deployment = pipelineParams.project
}
pipelineParams.registry = "harbor"
reg_prefix = "k8s-harbor01.gdfsxxds.rjyun/xy/"
pullSecret = "harbor"
if (!! pipelineParams.oversea){
http_proxy = "--build-arg HTTP_PROXY=localhost:3001"
} else {
http_proxy = ""
}
podTemplate(label: label, containers: [
containerTemplate(name: 'docker', image: 'docker:18', command: 'cat', ttyEnabled: true),
containerTemplate(name: 'kubectl', image: 'lachlanevenson/k8s-kubectl:v1.16.0', command: 'cat', ttyEnabled: true),
containerTemplate(name: 'helm', image: 'k8s-harbor01.gdfsxxds.rjyun/xy/helm:2.15.2', command: 'cat', ttyEnabled: true),
],
volumes: [
hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock')
]) {
node(label) {
def myRepo = checkout([$class: 'GitSCM', branches: [[name: "*/${pipelineParams.branch}"]], doGenerateSubmoduleConfigurations: false, extensions: [[$class: 'CloneOption', noTags: false, reference: '', shallow: true, timeout: 1000]]+[[$class: 'CheckoutOption', timeout: 1000]], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'glab_pass', url: pipelineParams.scmUrl]]])
def gitCommit = myRepo.GIT_COMMIT
def gitBranch = myRepo.GIT_BRANCH
def shortGitCommit = "${gitCommit[0..10]}"
def project = pipelineParams.scm
stage('Create docker images') {
gitlabCommitStatus {
container('docker') {
try{
withCredentials([[$class: 'UsernamePasswordMultiBinding',
credentialsId: pipelineParams.registry,
usernameVariable: 'DOCKER_HUB_USER',
passwordVariable: 'DOCKER_HUB_PASSWORD']]) {
retry(3) {
sh """
docker login -u ${DOCKER_HUB_USER} -p ${DOCKER_HUB_PASSWORD} ${reg_prefix}
docker build -t ${reg_prefix}${pipelineParams.project}:${shortGitCommit} .
docker push ${reg_prefix}${pipelineParams.project}:${shortGitCommit}
docker tag ${reg_prefix}${pipelineParams.project}:${shortGitCommit} ${reg_prefix}${pipelineParams.project}:latest
docker push ${reg_prefix}${pipelineParams.project}:latest
"""
}
}
}
catch(Exception e){
println(e.toString());
currentBuild.result = 'FAILURE'
step([$class: 'Mailer', notifyEveryUnstableBuild: true, recipients: pipelineParams.email, sendToIndividuals: true])
throw e
}
}
}
}
if (pipelineParams.helm) {
stage('Run helm') {
if (!!pipelineParams.helmArgs){
args = pipelineParams.helmArgs
} else {
args = ""
}
container('helm') {
try {
sh """
helm init --client-only --stable-repo-url=https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts
helm repo add donews http://chart.a99.com/
helm repo update
helm upgrade -i ${pipelineParams.project} ${pipelineParams.helm} \
--set image.repository=${reg_prefix}${pipelineParams.project},image.tag=${shortGitCommit} \
${args} \
--namespace ${pipelineParams.namespace}
"""
}
catch(Exception e) {
println(e.toString());
currentBuild.result = 'FAILURE'
step([$class: 'Mailer', notifyEveryUnstableBuild: true, recipients: pipelineParams.email, sendToIndividuals: true])
throw e
}
}
}
} else {
stage('Run kubectl') {
container('kubectl') {
try {
sh """
kubectl set image deployment/${pipelineParams.deployment}-deployment ${pipelineParams.project}=${reg_prefix}${pipelineParams.project}:${shortGitCommit} -n ${pipelineParams.namespace}
kubectl rollout status deployment ${pipelineParams.deployment}-deployment -n ${pipelineParams.namespace}
"""
}
catch(Exception e) {
println(e.toString());
currentBuild.result = 'FAILURE'
step([$class: 'Mailer', notifyEveryUnstableBuild: true, recipients: pipelineParams.email, sendToIndividuals: true])
throw e
}
}
}
}
currentBuild.result = 'SUCCESS'
step([$class: 'Mailer', notifyEveryUnstableBuild: true, recipients: pipelineParams.email, sendToIndividuals: true])
}
}
}
helm 自建chart 仓库,之前文章已介绍
前面文章: https://www.cnblogs.com/lixinliang/p/14304469.html
jenkins 部署多分支流水线项目
新建任务--> 起一个任务名称 duofenzhi -->选择多分支流水线
选择配置 -->分支源-->Git
等 1分钟,jenkins 将会自动拉取gitlab 代码进行编译构建
gitlab 添加自动触发,必须是 project :
在gitlab -->项目下-->settings-->Integrations-->增加 Webhooks
https://myjenkins.tagtic.cn/project/duofenzhi
查看 jenkins 已构建完成
查看k8s,容器已正常启动
项目部署完成。
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
第二种情况 gitlab 代码结构下没有 Jenkinsfile
#结构
gitlab 代码下只有Dockerfile ,没有Jenkinsfile,不能使用Jenkins 多分支构建,只能使用流水线
#新建立流水线
新建任务-->新建名称test -->流水线
GitLab Connection 选择 duoniu glab
选择构建触发器,这是和gitlab 打通的渠道
# 流水线
新增加 Pipeline script
helm{
scmUrl="https://glab.tag/test/mall_h5.git"
project="test"
email="test@do.com"
namespace="default"
branch="dev"
helm="donews/myapp"
helmArgs=""" --set service.port=80,ingress.hosts={mytest-xy.aaa.com}"""
}
注:
这个helm 定义将会自动加载上文的 helm.groovy 脚本,使用helm 部署容器,这和 jenkinsfile 是不同的,因为Jenkinsfile 不用配置 jenkins 的流水线脚本
查看 jenkins 已构建完成
查看k8s,容器已正常启动
项目部署完成。
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
第三种情况 gitlab 代码结构下也没有 Jenkinsfile,但是已经有 deployment svc ingress 这些配置
#新建流水线
新增加 Pipeline script
def label = "worker-${UUID.randomUUID().toString()}"
podTemplate(label: label, containers: [
containerTemplate(name: 'docker', image: 'docker', command: 'cat', ttyEnabled: true),
containerTemplate(name: 'kubectl', image: 'lachlanevenson/k8s-kubectl:v1.16.0', command: 'cat', ttyEnabled: true),
],
volumes: [
hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock')
]) {
node(label) {
def myRepo = checkout([$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [[$class: 'CloneOption', noTags: false, reference: '', shallow: true, timeout: 12000]]+[[$class: 'CheckoutOption', timeout: 7000]], submoduleCfg: [], userRemoteConfigs: [[credentialsId: 'glab_pass', url: 'https://glaxxx.cn/test/tgbusmall_api.git']]])
def gitCommit = myRepo.GIT_COMMIT
def gitBranch = myRepo.GIT_BRANCH
def shortGitCommit = "${gitCommit[0..10]}"
stage('Create docker images') {
gitlabCommitStatus {
container('docker') {
withCredentials([[$class: 'UsernamePasswordMultiBinding',
credentialsId: 'dockerreg',
usernameVariable: 'DOCKER_HUB_USER',
passwordVariable: 'DOCKER_HUB_PASSWORD']]) {
sh """
cp dockerfile/dockerfile-release/Dockerfile .
docker login -u ${DOCKER_HUB_USER} -p ${DOCKER_HUB_PASSWORD} k8s-harbor01.gdfsxxds.rjyun
docker build -t k8s-harbor01.gdfsxxds.rjyun/xy/tgbusmall-api:${shortGitCommit} .
docker push k8s-harbor01.gdfsxxds.rjyun/xy/tgbusmall-api:${shortGitCommit}
"""
}
}
}
}
stage('Run kubectl') {
container('kubectl') {
sh """
kubectl set image deployment/tgbusmall-api-deployment tgbusmall-api=k8s-harbor01.gdfsxxds.rjyun/xy/tgbusmall-api:${shortGitCommit} -n default
kubectl rollout status deployment tgbusmall-api-deployment -n default
"""
}
}
}
}
查看 jenkins 已构建完成
查看k8s,容器已正常更新
项目部署完成。
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
项目总结
第一种方式jenkins 多分支流水线配置简单,只用在gitlab 代码下定义好 Jenkinsfile, 适合多分支代码测试,便捷开发和测试人员,通过 groovy 可以自动化部署。
第二种方式 jenkins 流水线配置不用定义Jenkinsfile ,只用配置好 pipeline 内容即可,适合分支少项目,通过 groovy 也可以自动化部署。
第三种方式前提是已经有部署好的 deployment、svc 和ingress ,只需要每次进行镜像替换即可,不推荐使用,因为每部署一个新的项目必须先手动准备好这些必配文件,不使用 groovy 自动部署。