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