需求
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 自动部署。