学习笔记,仅供参考!
Jenkins Plugins
Kubernetes 安装
修改password密码点击保存!
安装插件:blue Ocean
点击Manage Jenkins --》点击插件管理 --》Available plugins --》搜索blue Ocean ----》点击安装
Updates:更新插件
Available plugins 下载并安装插件
Installed plugins 已安装的插件
Advanced settings 插件高级设置
blue Ocean:pipaline可视化管理工具
重启完成后进行k8s对接操作
如果jenkins是部署到k8s之上的那麽对接就不需要密钥对接和证书key(需要在jenkins设置里面配置特殊凭据(Certificate))把k8s的证书导出来进行制作,让后在导入到jenkins中
但是如果是部署到k8s之外的那麽对接就需要密钥对接
点击save保存
因为我们在初始安装的时候已经安装好了github的插件
点击构建新的流水线
需要下载对应的插件gitee
根据提示的gitee对接所需要的url 以及域名 和需要的凭证信息(在gitee设置里面配置,当然导入到jenkins中)可以实现对接
这里不再做操作
ps:如果想要部署完整版的可以去网上自行查询
ps因为我们用的是云服务器所以因为没有域名的缘故直接写的ip地址,就不需要写host
常用的代码仓库只有gitlab提供私有代码托管仓库的搭建 免费开源的
提示:如果后期需要配置钉钉消息,那麽应该在pod应用容器中将本地时间进行挂载在容器中,因为钉钉消息需要时间一致
- name: time-localtime
readOnly: true
mountPath: /etc/localtime
gitlab私有代码仓库的搭建:需要用到alertmanager nginx redis node-exporter 等等的一些组件搭建依赖postgres(部署harbor和sonarqube代码扫描 时有用到 ) redis
Docker
实现:部署postgres完成后数据库里自动的创建名为gitlab数据库
ps:(可以bash脚本的方式书写成config进行挂载到里面)
配置数据库用户和密码 以secret资源的方式创建 ps:加密
cat gitlab-secret.txt
postgres.user.root=root
postgres.pwd.root=redhat123
kubectl create -n jenkins secret generic gitlab-secret --from-env-file=gitlab-secret.txt
generic:手动
部署postgres完成后数据库里自动的创建名为gitlab数据库
实现1:
spec:
initContainers:
- name: install
image: busybox
command:
- sh
- "-c"
- |
set -e
cat > /docker-entrypoint-initdb.d/init-user-db.sh << EOF
POSTGRES_USER=root
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<-EOSSQL
CREATE DATABASE gitlab;
EOSSQL
EOF
volumeMounts:
- name: install
mountPath: /docker-entrypoint-initdb.d
在容器下面
volumeMounts:
- name: install
mountPath: /docker-entrypoint-initdb.d
volumes:
- name: install
emptyDir: {}
\ psql \l
实现2:
cat init-user-db.sh
#!/bin/bash
set -e
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<-EOSQL
CREATE DATABASE gitlab;
EOSQL
kubectl create -n jenkins configmap init-db --from-file init-user-db.sh
cat postgres-statefulset.yaml
apiVersion: v1
kind: Service
metadata:
name: postgres-headless
namespace: jenkins
labels:
app: postges
spec:
type: ClusterIP
clusterIP: None
ports:
- port: 5432
name: postgres
targetPort: 5432
selector:
app: postgres
---
apiVersion: v1
kind: Service
metadata:
name: postgres-svc
labels:
app: postgres
namespace: jenkins
spec:
type: ClusterIP
ports:
- name: server
port: 5432
targetPort: 5432
#protocol: TCP
# nodePort: 32088
clusterIP: 10.97.235.67
selector:
app: postgres
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
namespace: jenkins
name: postgres-statefulset
labels:
app: postgres
spec:
replicas: 1
selector:
matchLabels:
app: postgres
serviceName: postgres-headless
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:11.4 #若本地没有启动该仓库,换成postgres:11.4
imagePullPolicy: "IfNotPresent"
ports:
- containerPort: 5432
env:
- name: POSTGRES_USER #PostgreSQL 用户名
valueFrom:
secretKeyRef:
name: gitlab-secret
key: postgres.user.root
- name: POSTGRES_PASSWORD #PostgreSQL 密码
valueFrom:
secretKeyRef:
name: gitlab-secret
key: postgres.user.root
resources:
limits:
cpu: 1000m
memory: 2048Mi
requests:
cpu: 50m
memory: 100Mi
volumeMounts:
- name: init-db
mountPath: /docker-entrypoint-initdb.d/init-user-db.sh
subPath: init-user-db.sh
- mountPath: /var/lib/postgresql/data
name: postgres-nfs
- name: time-localtime
readOnly: true
mountPath: /etc/localtime
volumes:
- name: init-db
configMap:
name: init-db
# - name: postgre-nfs
# mountPath: /var/lib/postgresql/data
- name: time-localtime
hostPath:
path: /etc/localtime
volumeClaimTemplates:
- metadata:
name: postgres-nfs
spec:
accessModes: [ "ReadWriteMany" ]
storageClassName: "nfs-storageclass"
resources:
requests:
storage: 3Gi
cat redis-statefults.yaml
apiVersion: v1
kind: Service
metadata:
name: redis-headless
namespace: jenkins
labels:
app: redis
spec:
type: ClusterIP
clusterIP: None # 创建无头服务,如果需要对外暴露端口可自行创建service
ports:
- name: redis
port: 6379
targetPort: redis
selector:
app: redis
---
apiVersion: v1
kind: Service
metadata:
name: redis-svc
namespace: jenkins
labels:
app: redis
spec:
type: ClusterIP
ports:
- port: 6379
targetPort: 6379
clusterIP: 10.110.1.132
selector:
app: redis
---
apiVersion: v1
kind: ConfigMap
metadata:
name: redis-config
namespace: jenkins
labels:
app: redis
data:
redis.conf: |
bind 0.0.0.0
protected-mode yes
port 6379
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: redis
namespace: jenkins
spec:
selector:
matchLabels:
app: redis
serviceName: redis-headless
replicas: 2
updateStrategy:
type: RollingUpdate
template:
metadata:
name: redis
labels:
app: redis
spec:
affinity:
podAntiAffinity: #pod的反亲和性
requiredDuringSchedulingIgnoredDuringExecution: #硬亲和性(硬策略)
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- redis
topologyKey: "kubernetes.io/hostname"
containers:
- name: redis
image: redis:5.0.13
imagePullPolicy: "IfNotPresent"
ports:
- name: redis
containerPort: 6379
volumeMounts:
- name: data
mountPath: /etc/redis/
- name: "redis-data"
mountPath: "/data"
volumes:
- name: data
configMap:
name: redis-config
volumeClaimTemplates:
- metadata:
name: redis-data
spec:
accessModes: [ "ReadWriteMany" ]
storageClassName: "nfs-storageclass"
resources:
requests:
storage: 200M
cat gitlab-statefulset.yaml
---
apiVersion: v1
kind: Service
metadata:
name: gitlab-headless
namespace: jenkins
labels:
app: gitlab
spec:
clusterIP: None
ports:
- port: 80
name: postgres
targetPort: 80
selector:
app: gitlab
---
apiVersion: v1
kind: Service
metadata:
name: gitlab-svc
labels:
app: gitlab
namespace: jenkins
spec:
type: NodePort
ports:
- name: server
port: 80
targetPort: 80
protocol: TCP
nodePort: 32085
clusterIP: 10.99.69.119
selector:
app: gitlab
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
namespace: jenkins
name: gitlab
labels:
app: gitlab
spec:
replicas: 1
selector:
matchLabels:
app: gitlab
serviceName: gitlab-headless
template:
metadata:
labels:
app: gitlab
spec:
containers:
- name: gitlab
image: sameersbn/gitlab:13.2.2
imagePullPolicy: "IfNotPresent"
env:
- name: GITLAB_HOST
value: "gitlab-svc"
- name: GITLAB_PORT
value: "80"
- name: GITLAB_SECRETS_DB_KEY_BASE
value: "long-and-random-alpha-numeric-string"
- name: GITLAB_SECRETS_DB_KEY_BASE
value: "long-and-random-alpha-numeric-string"
- name: GITLAB_SECRETS_SECRET_KEY_BASE
value: "long-and-random-alpha-numeric-string"
- name: GITLAB_SECRETS_OTP_KEY_BASE
value: "long-and-random-alpha-numeric-string"
- name: DB_HOST
value: "postgres-svc" #
- name: DB_NAME
value: "gitlab"
- name: DB_USER
valueFrom:
secretKeyRef:
name: gitlab-secret2
key: username
- name: DB_PASS
valueFrom:
secretKeyRef:
name: gitlab-secret2
key: password
- name: REDIS_HOST
value: "redis-svc"
- name: REDIS_PORT
value: "6379"
ports:
- containerPort: 80
resources:
limits:
cpu: 2000m
memory: 5048Mi
requests:
cpu: 100m
memory: 500Mi
volumeMounts:
- mountPath: /home/git/data
name: gitlab-nfs
- name: time-localtime
readOnly: true
mountPath: /etc/localtime
volumes:
- name: time-localtime
hostPath:
path: /etc/localtime
volumeClaimTemplates:
- metadata:
name: gitlab-nfs
spec:
accessModes: [ "ReadWriteMany" ]
storageClassName: "nfs-storageclass"
resources:
requests:
storage: 3Gi
部署和注册gitlab完成后点击Create a project创建一个仓库
点击Create Project创建
(因为往我们部署的私有仓库里面进行导入代码需要git命令 所有需要在控制或工作节点都下载上git命令 yum install git -y)
去要下载的平台上 在要下载的代码列表里面点击克隆或者下载按钮,复制下载连接地址
直接在master上下载 让后解压 : unzip +压缩包 (yum search unzip)
把解压出来的代码上传到私密仓库中
在解压出来的代码目录中执行我们部署的gitlab提供的git命令(一定要在本目录执行!!!)
Create a new repository创建新的存储库
Push an existing folder推送现有文件夹
Push an existing Git repository推送现有的Git存储库
根据我们的部署我们是要推送gitee下载的代码进行推送,所以使用第二个哐框里面的命令
git init 代码初始化
git remote add origin http://gitlab-svc/root/myblog.git 生成一个git地址
(在执行这个命令时如果出现fatal: unable to access 'http://gitlab-svc/root/myblog.git/': Could not resolve host: gitlab-svc; Unknown error
需要将此命令的http://gitlab-svc/root/myblog.git修改为内部ip来使用)
git remote -v 查看git地址
git add .
git commit -m "Initial commit" 初始化 git commit -m "updata Dockerfile(指定上传的包)"
git push -u origin master #出现以下error 在host里面写指定
在jenkins下载gitlab插件(不需要重启)
安装完成后点击系统管理---》系统配置中
这里的URL:我写的是gitlab-svc的地址,如果配置了ingress的话写域名
在gitlab创建token
复制创建好的token到jenkins上面
点击Test Connection进行连接测试
ps:如果失败就可能是需要在host主机列表里面书写映射ip
或者kubectl edit -n kube-system cm coredns #里面添加静态映射
hosts {
192.168.10.30 jenkins.smile.com gitlab.smile.com
fallthrough
}
想要快速生效就删除
kubectl delete pods -n kube-system coredns-7f89b7bc75-
至此jenkins和gitlab成功打通
点击新建任务
源码管理选择git
添加凭证
这里的URL:有ingress就写域名 没有就写无头或者svc地址 gitlab的
选择构建触发器
点击 Build when a change is pushed to GitLab. GitLab webhook URL: http://123.60.162.77:32080/project/freeeee? #当代码发生变化的时候向gitlab进行推送 #因为地址的原因不能直接进行推送,需要配置wedhooks
在wedhooks里面配置上个图片中的url地址进行创建
ps:Url is blocked:Requests to the local network are not allowed
如果出现这个错误:是因为本地网络连接不允许,他是从一个安全方面的进行一个考量
解决方法: 点击此页面左上角的小扳手 点击左边的状态栏 Settings设置 点击里面的network
jenkins点击save
测试:看到200 成功
ps:构建过后的代码存放在jenkins动态存储类中/workspace/freeeee目录中
钉钉创建自定义机器人!!!
win 查询公网ip命令:curl ifconfig.me
自定义机器人接入 - 钉钉开放平台 (dingtalk.com)
curl 'https://oapi.dingtalk.com/robot/send?access_token=xxxxxxxx' \
-H 'Content-Type: application/json' \
-d '{"msgtype": "text","text": {"content":"我就是我, 是不一样的烟火"}}'
ps⛵️创建钉钉机器人的ip段要尤其注意 !!!!!!正常情况下在本地搭建的需要写的win公网ip
因为jenkins目前只有一个master节点 ,所以的工作都来自于master节点,master处于单点状态!!很危险
创建一个名为k8s-slave1的节点 并勾选固定节点
jenkins执行任务时候严重依赖标签
Jenkins 可以在此节点上执行并发构建的最大数目 #同时可以跑多个任务
点击创建好的节点上面进行配置
在选订好的节点上进行配置,将其配置成 从节点
Or run from agent command line, with the secret stored in a file:
echo a763cb84fc791b919eb64532910af0e09515c395684ec66b1a231dbed5824091 > secret-file
curl -sO http://123.60.162.77:32080/jnlpJars/agent.jar
#因为我部署在云服务器 所以123.60.162.77:32080尽量用内网,如果是本地部署的需要在host里面写ingres地址做映射
java -jar agent.jar -jnlpUrl http://123.60.162.77:32080/manage/computer/k8s%2Dslave1/jenkins-agent.jnlp -secret @secret-file -workDir "/opt/jenkins_jobs"
在执行此命令同时需要下载java环境yum install java-11-openjdk -y java --version
java -jar agent.jar -jnlpUrl http://123.60.162.77:32080/manage/computer/k8s%2Dslave1/jenkins-agent.jnlp -secret @secret-file -workDir "/opt/jenkins_jobs"
当在本地或者云服务器部署执行此命令的时候会出现连接不上(50000端口)
解决办法:
(记得写完成加端口号50000 10.99.93.125:50000)点击保存就好
成功(可以写多个从节点)
但是因为java -jar agent.jar -jnlpUrl http://123.60.162.77:32080/manage/computer/k8s%2Dslave1/jenkins-agent.jnlp -secret @secret-file -workDir "/opt/jenkins_jobs"
给定的命令是前台运行不符合工作使用,很不方便 所以要将其修改为后台运行!
nohup java -jar agent.jar -jnlpUrl http://123.60.162.77:32080/manage/computer/k8s%2Dslave1/jenkins-agent.jnlp -secret @secret-file -workDir "/opt/jenkins_jobs" &
让后修改我们直接构建的任务 让他能够使用从节点
点击保存
在开始构建之前要在从节点上安装git命令 因为我们需要从仓库克隆下面,需要用到git
yum install git -y
以上都是我们手动触发的任务,我们要的是实现自动触发
测试:往gitlab仓库里面上传代码
将准备好的代码或者dockerfile上传到master节点上面的仓库目录里面
git status #查看是否有新的文件需要上传
git add .
git status
git commit -am 'add Dockerfile'
git push #输入gitlab密码和账户进行上传
呈上启下 :以上的操作仅仅只是将代码从仓库里面下载到了本地 没有其他的操作
但是我们希望把代码下载到本地后的通过制作镜像,上传到镜像仓库 ,并部署到k8s上面!
Scripted Pipeline 脚本式流水线 最初的形态
Declarative Pipeline: 声明式流水线 (类似于Dockerfile)
pipeline脚本:的实现方式是通过Groovy DSL (领域专用语言)写的,所有的发布流程都可以有一段Groovy脚本,通过wed.ui的方式表述出来
ps:一个程序员在自己的node boot上面对代码进行了更新,把代码推送到gitlab上面,由我们部署的jenkins自动的获取到我们更新的代码, gitlab和jenkins要产生关联/一个认证的关系
jenkins所有的功能基本上都是以插件的形势来实现的
触发器(jenkins):当gitlab仓库代码发生更新的时候会自动的触发jenkins进行代码拉取,jenkins之所以能够拉取gitlab仓库实时更新的代码是因为构建了一个任务类型的触发器
gitlab_wed_hooks: jenkins触发器是通过gitlab的wed-hooks来进行勾连,或对接
只要代码一发生改变就会触发流水线
jenkins ---》往gitlab拉取更新的代码 ---》让后制作成镜像 ---》将镜像上传到镜像仓库中(私有公有)---》 部署到k8s 实现对外的访问
pipeline {
agent {label 'k8s-slave1'} #any 可以写多个从节点,在任意多个从节点上运行
environment {
PROJECT = 'myblog'
}
stages {
stage('Checkout') { #第一步 获取代码
steps {
checkout scm #检测代码 scm是一个特殊代码 由pipeline特殊执行
}
}
stage('Build') { #第二步 通过代码构建镜像
steps {
sh 'make'
}
}
stage('Test'){ #第三步 上传镜像至镜像仓库(harbor)
steps {
sh 'make check'
junit 'reports/**/*.xml'
}
}
stage('Deploy') { #第四步 把镜像部署在K8S集群中
steps {
sh 'make publish'
}
}
}
post { #声明 定义一个steps或多个steps 根据流水线的阶段来进行判断 成功发什么,失败了又发什么
success {
echo 'Congratulations!'
}
failure {
echo 'Oh no!'
}
always {
echo 'I will always say Hello again!'
}
}
}
声明式流水线范例:
pipeline {
agent {label 'k8s-slave1'} #agent标签代理,声明是由那个agent(从节点)来执行,label标签跟从节点配置的标签一致 #通过标签来选择 属于全局定义 可以在每个stages阶段下面写agent来单独声明由那个节点执行 !
environment { #环境变量
PROJECT = 'myblog'
}
stages { #阶段 分段执行 一个stages里面可以包含多个stage
stage('printenv') { #将本地的所有环境变量都输出出来
steps {
echo 'Hello World'
sh 'printenv'
}
}
stage('check') { #当gitlab代码仓库内容发生改变的话,jenkins自动获取拉取代码
steps {
checkout scmGit(branches: [[name: '*/master']], extensions: [], userRemoteConfigs: [[credentialsId: 'gitlab-user', url: 'http://gitlab.smile.com/root/myblog.git']]) #可以通过jenkins自动生成
}
}
stage('build-image') {
steps {
sh 'docker build . -t myblog:latest -f Dockerfile'
}
}
stage('send-msg') {
steps {
sh """
curl 'https://oapi.dingtalk.com/robot/send?access_token=' \
-H 'Content-Type: application/json' \
-d '{"msgtype": "text",
"text": {
"content": "您有新的代码更新"
}
}'
"""
}
}
}
}
options :允许从流水线内部配置特定于流水线的选项 {timeout: 设置流水线的运行超时时间,工作阶段一般会在30分钟 }
书写格式: options { timent(time: 1, unit: 'HOURS')} 设置允许超时时间为一个小时
restry: 可以失败的次数 书写方式: options { retry(3) }s
在构建单分支流水线之前,选择禁用之前创建的任务
创建流水线的方式有两种:
1.在wed UI上面直接书写脚本Pipeline script
2.不需要在jenkins wed-ui里面书写脚本,通过设置Pipeline script from SCM在节点上面书写脚本上传到gitlab仓库
checkout scmGit:
{checkout scmGit(branches: [[name: '*/master']], extensions: [], userRemoteConfigs: [[credentialsId: 'gitlab-user', url: 'http://gitlab.smile.com/root/myblog.git']])} #此代码可以使用jenkins自行生成
点击Pipeline流水线下面的流水线语法----》 找到并点击checkout
ps⛵️:请自行将Dockerfile文件上传致要执行的节点上面并上传到gitlab仓库中
master(源码存放节点):git commit -am 'updata Dockerfile'
cat Dockerfile
# Base images 基础镜像
FROM centos:centos7.5.1804
#MAINTAINER 维护者信息
LABEL maintainer="[email protected]"
#ENV 设置环境变量
ENV LANG en_US.UTF-8
ENV LC_ALL en_US.UTF-8
#RUN 执行以下命令
RUN curl -so /etc/yum.repos.d/Centos-7.repo http://mirrors.aliyun.com/repo/Centos-7.repo && rpm -Uvh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm
RUN yum install -y python36 python3-devel gcc pcre-devel zlib-devel make net-tools nginx
#工作目录
WORKDIR /opt/myblog
#拷贝文件至工作目录
COPY . . #把当前node1里面的内容直接copy到/opt/jen_jobs
# 拷贝nginx配置文件
COPY myblog.conf /etc/nginx
#安装依赖的插件
RUN pip3 install -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com -r requirements.txt
RUN chmod +x run.sh && rm -rf ~/.cache/pip
#EXPOSE 映射端口
EXPOSE 8002
#容器启动时执行命令
CMD ["./run.sh"]
点击save保存 并点击立即构建
以上操作完成任务的描述: 从代码仓库下载代码到本地 然后通过dockerfile构建镜像
ps:还可以通过Pipeline流水线的图形界面形势来查看整体的结果
#缺点:对于阶段执行的描述不是特别完整
在Pipeline流水线的图形界面时,当个别阶段执行出错/失败的时候可以单独的重启此阶段让他重新跑完
Pipeline script from SCM:
将jenkinsfile 上传到源码节点的存放目录里面 ,并上传到gitlab里面
cat Jenkinsfile
pipeline {
agent {label 'k8s-slave1'}
environment {
PROJECT = 'myblog'
}
stages {
stage('printenv') {
steps {
echo 'Hello World'
sh 'printenv'
}
}
stage('check') {
steps {
checkout scmGit(branches: [[name: '*/master']], extensions: [], userRemoteConfigs: [[credentialsId: 'gitlab-user', url: 'http://gitlab.smile.com/root/myblog.git']])
}
}
stage('build-image') {
steps {
sh 'docker build . -t myblog:latest -f Dockerfile'
}
}
stage('send-msg') {
steps {
sh """
curl 'https://oapi.dingtalk.com/robot/send?access_token=' \
-H 'Content-Type: application/json' \
-d '{"msgtype": "text",
"text": {
"content": "您有新的代码更新"
}
}'
"""
}
}
}
}
git add .
git remote -v
git status
git push
优化:Pipeline流水线优化
从jenkinsfile:
问题1.:sh 'docker build . -t myblog:latest -f Dockerfile' #tag latest
以上的build构建的镜像因为每次构建的tag都是latest容易被覆盖 造成悬空镜像 永远只有一个镜像,造成版本无法后滚操作,当新版本出现问题之后无法回退
解决方法:使用环境变量myblog:${GIT_COMMIT} 是gitlab自带的
我们可以以{commit}id来作为镜像的版本号来使用 #tag
sh 'docker build . -t myblog:${GIT_COMMIT} -f Dockerfile'
在源码存放节点 jenkinsfile文件中进行修改
将jenkinsfile 上传到源码节点的存放目录里面 ,并上传到gitlab里面
cat Jenkinsfile
pipeline {
agent {label 'k8s-slave1'}
environment {
PROJECT = 'myblog'
}
stages {
stage('printenv') {
steps {
echo 'Hello World'
sh 'printenv'
}
}
stage('check') {
steps {
checkout scm
}
}
stage('build-image') {
steps {
sh 'docker build . -t myblog:${GIT_COMMIT} -f Dockerfile'
}
}
stage('send-msg') {
steps {
sh """
curl 'https://oapi.dingtalk.com/robot/send?access_token=' \
-H 'Content-Type: application/json' \
-d '{"msgtype": "text",
"text": {
"content": "您有新的代码更新"
}
}'
"""
}
}
}
}
git add .
git remote -v
git commit -am 'updata Jenkinsfile'
git status
git push
问题2.stage('send-msg') { #美化
向钉钉发送消息,使始终是以stage的形势来发送 ,如果错了就不会继续执行下面的消息 但是我们希望正确的发送就发送正确,错误就发送失败了,然后不会影响下面的执行
这个时候就用到了post:(根据流水线或者阶段的完成情况而运行而运行post)
cat Jenkinsfile #在源代码存放节点
pipeline {
agent { label 'k8s-slave1'}
stages {
stage('printenv') {
steps {
echo 'Hello World'
sh 'printenv'
}
}
stage('check') {
steps {
checkout scm
}
}
stage('build-image') {
steps {
retry(2) { sh 'docker build . -t myblog:${GIT_COMMIT}'} #可以失败两次
}
}
}
post {
success {
echo 'Congratulations!'
sh """
curl 'https://oapi.dingtalk.com/robot/send?access_token=' \
-H 'Content-Type: application/json' \
-d '{"msgtype": "text",
"text": {
"content": "构建成功\n 关键字:smile\n 项目名称: ${JOB_BASE_NAME}\n Commit Id: ${GIT_COMMIT}\n 构建地址:${RUN_DISPLAY_URL}"
}
}'
"""
}
failure {
echo 'Oh no!'
sh """
curl 'https://oapi.dingtalk.com/robot/send?access_token=' \
-H 'Content-Type: application/json' \
-d '{"msgtype": "text",
"text": {
"content": "❌构建失败❌\n 关键字:smile\n 项目名称: ${JOB_BASE_NAME}\n Commit Id: ${GIT_COMMIT}\n 构建地址:${RUN_DISPLAY_URL}"
}
}'
"""
}
always {
echo 'I will always say Hello again!'
}
}
}
git commit -am 'updata Jenkinsfile'
git status
git push
公有镜像仓库 dockerhub
想要上传镜像首先要给镜像修改名字
docker tag myblog:4ba71b845bc07a86e7ad9101bf1a2227be43c896 1008611zmx/myblog:v1.0
docker login -u 1008611zmx -p
docker push 1008611zmx/myblog:v1.0
cat Jenkinsfile
pipeline {
agent { label 'k8s-slave1'}
stages {
stage('printenv') {
steps {
echo 'Hello World'
sh 'printenv'
}
}
stage('check') {
steps {
checkout scm
}
}
stage('build-image') {
steps {
retry(2) { sh 'docker build . -t 1008611zmx/myblog:${GIT_COMMIT}'}
}
}
stage('builid-image') {
steps {
retry(2) { sh 'docker login -u 1008611zmx -p '}
retry(2) { sh 'docker push 1008611zmx/myblog:${GIT_COMMIT}'}
}
}
}
post {
success {
echo 'Congratulations!'
sh """
curl 'https://oapi.dingtalk.com/robot/send?access_token=' \
-H 'Content-Type: application/json' \
-d '{"msgtype": "text",
"text": {
"content": "构建成功\n 关键字:smile\n 项目名称: ${JOB_BASE_NAME}\n Commit Id: ${GIT_COMMIT}\n 构建地址:${RUN_DISPLAY_URL}"
}
}'
"""
}
failure {
echo 'Oh no!'
sh """
curl 'https://oapi.dingtalk.com/robot/send?access_token=' \
-H 'Content-Type: application/json' \
-d '{"msgtype": "text",
"text": {
"content": "❌构建失败❌\n 关键字:smile\n 项目名称: ${JOB_BASE_NAME}\n Commit Id: ${GIT_COMMIT}\n 构建地址:${RUN_DISPLAY_URL}"
}
}'
"""
}
always {
echo 'I will always say Hello again!'
}
}
}
git commit -am 'updata Jenkinsfile'
git status
git push
持续优化: 账户和密码 'docker login -u -p ' 和钉钉发送消息的token值 暴漏,存在安全隐患
创建两个包含docker账号密码和钉钉的token值的凭证 #通过环境变量的方式进行调用
environment {
BITBUCKET_COMMON_CREDS = credentials('jenkins-bitbucket-common-creds')
}
实际设置中包含了三个变量:
BITBUCKET_COMMON_CREDS -包含一个以冒号分隔的用户名和密码,格式为username:password
BITBUCKET_COMMON_CREDS_USR -附加的一个仅包含用户名部分的变量
BITBUCKET_COMMON_CREDS_PSW -附加的一个仅包含密码部分的变量
cat Jenkinsfile
pipeline {
agent { label 'k8s-slave1'}
environment {
DOCKERHUB_CREDS = credentials('dockerhub')
DINGTALK_CREDS = credentials('dingtalk')
}
stages {
stage('printenv') {
steps {
echo 'Hello World'
sh 'printenv'
}
}
stage('check') {
steps {
checkout scm
}
}
stage('build-image') {
steps {
retry(2) { sh 'docker build . -t 1008611zmx/myblog:${GIT_COMMIT}'}
}
}
stage('push-image') {
steps {
retry(2) { sh 'docker login -u ${DOCKERHUB_CREDS_USR} -p ${DOCKERHUB_CREDS_PSW}'}
retry(2) { sh 'docker push 1008611zmx/myblog:${GIT_COMMIT}'}
}
}
}
post {
success {
echo 'Congratulations!'
sh """
curl 'https://oapi.dingtalk.com/robot/send?access_token=${DINGTALK_CREDS_PSW}' \
-H 'Content-Type: application/json' \
-d '{"msgtype": "text",
"text": {
"content": "????构建成功????\n 关键字:smile\n 项目名称: ${JOB_BASE_NAME}\n Commit Id: ${GIT_COMMIT}\n 构建地址:${RUN_DISPLAY_URL}"
}
}'
"""
}
failure {
echo 'Oh no!'
sh """
curl 'https://oapi.dingtalk.com/robot/send?access_token=${DINGTALK_CREDS_PSW}' \
-H 'Content-Type: application/json' \
-d '{"msgtype": "text",
"text": {
"content": "???构建失败???\n 关键字:smile\n 项目名称: ${JOB_BASE_NAME}\n Commit Id: ${GIT_COMMIT}\n 构建地址:${RUN_DISPLAY_URL}"
}
}'
"""
}
always {
echo 'I will always say Hello again!'
}
}
}
私有镜像仓库 harbor
curl -L https://github.com/docker/compose/releases/download/1.18.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
yum install docker-compose -y
tar -zxvf harbor-offline-installer-v2.5.3.tgz
配置 :harbor.yml
安装./install.sh
导入准备好的yaml文件 ;deploy-myblog.yaml deploy-mysql.yaml
先执行deploy-mysql.yaml
stage('deploy') {
steps {
sh "sed -i 's#{{IMAGE}}#1008611zmx/myblog:${GIT_COMMIT}#g' deploy/*" #deploy/*是指在deploy目录下面存放的yaml文件都修改并执行 deploy/*存放yaml的目录 /*匹配所有
cat Jenkinsfile
pipeline {
agent { label 'k8s-slave1'} #######
environment {
DOCKERHUB_CREDS = credentials('dockerhub')
DINGTALK_CREDS = credentials('dingtalk')
}
stages {
stage('printenv') {
steps {
echo 'Hello World'
sh 'printenv'
}
}
stage('check') {
steps {
checkout scm
}
}
stage('build-image') {
steps {
retry(2) { sh 'docker build . -t 1008611zmx/myblog:${GIT_COMMIT}'}
}
}
stage('push-image') {
steps {
retry(2) { sh 'docker login -u ${DOCKERHUB_CREDS_USR} -p ${DOCKERHUB_CREDS_PSW}'}
retry(2) { sh 'docker push 1008611zmx/myblog:${GIT_COMMIT}'}
}
}
stage('deploy') {
steps {
sh "sed -i 's#{{IMAGE_URL}}#1008611zmx/myblog:${GIT_COMMIT}#g' deploy/deploy-myblog.yaml"
timeout(time: 1, unit: 'MINUTES') {
sh "kubectl apply -f deploy/deploy-myblog.yaml"
}
}
}
}
post {
success {
echo 'Congratulations!'
sh """
curl 'https://oapi.dingtalk.com/robot/send?access_token=${DINGTALK_CREDS_PSW}' \
-H 'Content-Type: application/json' \
-d '{"msgtype": "text",
"text": {
"content": "构建成功\n 关键字:smile\n 项目名称: ${JOB_BASE_NAME}\n Commit Id: ${GIT_COMMIT}\n 构建地址:${RUN_DISPLAY_URL}"
}
}'
"""
}
failure {
echo 'Oh no!'
sh """
curl 'https://oapi.dingtalk.com/robot/send?access_token=${DINGTALK_CREDS_PSW}' \
-H 'Content-Type: application/json' \
-d '{"msgtype": "text",
"text": {
"content": "❌构建失败❌\n 关键字:smile\n 项目名称: ${JOB_BASE_NAME}\n Commit Id: ${GIT_COMMIT}\n 构建地址:${RUN_DISPLAY_URL}"
}
}'
"""
}
always {
echo 'I will always say Hello again!'
}
}
}
git add . #因为加入的有新的目录
git status
git commit -am 'add depoly'
git push
ps️:因为我们是写的流水线脚本是从节点上执行的,但是从节点上面没有kubectl命令,所以会报错
解决办法: 在从节点上面创建/root/.kube 把master节点上面/root/.kube目录里面的config文件copy到从节点上面 从节点就可以使用k8s命令有了k8s权限 #但是极其不推荐,相当于从节点有了master权限
cat deploy-myblog.yaml
apiVersion: v1
kind: Namespace
metadata:
name: develop
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: myblog
namespace: develop
spec:
replicas: 1 #指定Pod副本数
selector: #指定Pod的选择器
matchLabels:
app: myblog
template:
metadata:
labels: #给Pod打label
app: myblog
spec:
containers:
- name: myblog
image: {{IMAGE_URL}} ## 修改
imagePullPolicy: IfNotPresent
env:
- name: MYSQL_HOST
value: "10.104.238.30"
- name: MYSQL_PORT
value: "3306"
#- name: MYSQL_USER
#value: "root"
- name: MYSQL_PASSWD
value: "123456"
ports:
- containerPort: 8002
resources:
requests:
memory: 100Mi
cpu: 50m
limits:
memory: 500Mi
cpu: 100m
livenessProbe:
httpGet:
path: /blog/index/
port: 8002
scheme: HTTP
initialDelaySeconds: 10 # 容器启动后第一次执行探测是需要等待多少秒
periodSeconds: 15 # 执行探测的频率
timeoutSeconds: 2 # 探测超时时间
readinessProbe:
httpGet:
path: /blog/index/
port: 8002
scheme: HTTP
initialDelaySeconds: 10
timeoutSeconds: 2
periodSeconds: 15
git commit -am 'updata Jenkinsfile'
git push
至此完成!
测试环境 生产环境
测试/生产环境通过命名空间namespace进行区分
ps:当部署的gpmall的项目出现了问题,要在测试环境进行调整/测试,完成后与生产环境的代码相互集成校验
代码提交的之后代码应该先往测试环境部署进行测试 (代码严格禁止在源代码上进行修改,修改源代码的复制品),如果测试环境的代码没有问题的话,在将其通过生产流水线,部署到生产环境中去,如果生产环境下的代码出现问题后,要修改测试环境的源代码的复制品进行更新,进行循环测试,当更新的代码没有问题后,把源代码复制器的更新,真正的源代码进行更新,进行合并,推送给生产环境的流水线进行部署!
根据以上定义:我们至少需要两条流水线
我们可以构建两条流水线,但是这样的方式很不好,不利于我们后面的持续集成,从而引出多分支!
先将之前构建的单分支流水线进行禁用操作~
然后在源代码存放的节点上面创建代码仓库分支
git log 查看操作步骤
git branch 查看分支 * 指到/处于那个分支就表示正在那个分支上面
git checkout -b develop 创建名为develop的分支
:Switched to a new branch 'develop' #产生了一个新的分支
git checkout master 切换分支 到master上面
:Switched to branch 'master'
git branch
develop
* master
git push --set-upstream origin develop 上传/更新分支到代码仓库上面
在develop分支上面修改的内容不会影响到master分支的代码仓库
git merge 代码合并
配置多分支流水线
发现分支 add 选择正则表达式 因为只有几个分支所有就写.* 匹配所有 当分支多的时候可以写成 master|develop这种格式
点击保存会自动扫描
效果实现: 我们在源代码节点上面修改master分支的代码 会触发jenkins 多分支流水线中master任务
我们在源代码节点上面修改develog分支的代码 会触发jenkins 多分支流水线中develog任务
在原来单分支流水线上面的的流水线脚本进行美化
将Jenkinsfile写好的脚本上传到develop分支上面 #把原来的Jenkinsfile转移到其他的目录里面保证当我们上传的新的流水线脚本发现问题会后能够回滚
pipeline {
agent { label 'k8s-slave1'}
environment {
IMAGE_REPO = "1008611zmx/myblog"
DINGTALK_CREDS = credentials('dingtalk')
DOCKERHUB_CREDS = credentials('dockerhub')
TAB_STR = "\n \n " #换行
}
stages {
stage('printenv') {
steps {
script{
sh "git log --oneline -n 1 > gitlog.file" #显示第一行到gitlog里面
env.GIT_LOG = readFile("gitlog.file").trim() #env.GIT_LOG 读取gitlog.file trim的意思是修剪 美化
}
sh 'printenv'
}
}
stage('checkout') {
steps {
checkout scm
script{
env.BUILD_TASKS = env.STAGE_NAME + "√..." + env.TAB_STR
}
}
}
stage('build-image') {
steps {
retry(2) { sh 'docker build . -t ${IMAGE_REPO}:${GIT_COMMIT}'}
script{
env.BUILD_TASKS += env.STAGE_NAME + "√..." + env.TAB_STR #(a=沙
a += 淼 +=是在原来的基础上对变量的新一轮赋值
a=沙淼)
}
}
}
stage('push-image') {
steps {
retry(2) { sh 'docker login -u ${DOCKERHUB_CREDS_USR} -p ${DOCKERHUB_CREDS_PSW}'}
retry(2) { sh 'docker push ${IMAGE_REPO}:${GIT_COMMIT}'}
script{
env.BUILD_TASKS += env.STAGE_NAME + "√..." + env.TAB_STR
}
}
}
stage('deploy') {
steps {
sh "sed -i 's#{{IMAGE_URL}}#${IMAGE_REPO}:${GIT_COMMIT}#g' deploy/*"
timeout(time: 1, unit: 'MINUTES') {
sh "kubectl apply -f deploy/"
}
script{
env.BUILD_TASKS += env.STAGE_NAME + "√..." + env.TAB_STR
}
}
}
}
post {
success {
echo 'Congratulations!'
sh """
curl 'https://oapi.dingtalk.com/robot/send?access_token=${DINGTALK_CREDS_PSW}' \
-H 'Content-Type: application/json' \
-d '{
"msgtype": "markdown",
"markdown": {
"title":"myblog",
"text": " 构建成功 \n**项目名称**:smile \n**Git log**: ${GIT_LOG} \n**构建分支**: ${GIT_BRANCH} \n**构建地址**:${RUN_DISPLAY_URL} \n**构建任务**:${BUILD_TASKS}"
}
}'
"""
}
failure {
echo 'Oh no!'
sh """
curl 'https://oapi.dingtalk.com/robot/send?access_token=${DINGTALK_CREDS_PSW}' \
-H 'Content-Type: application/json' \
-d '{
"msgtype": "markdown",
"markdown": {
"title":"myblog",
"text": "❌ 构建失败 ❌ \n**项目名称**:smile \n**Git log**: ${GIT_LOG} \n**构建分支**: ${GIT_BRANCH} \n**构建地址**:${RUN_DISPLAY_URL} \n**构建任务**:${BUILD_TASKS}"
}
}'
"""
}
always {
echo 'I will always say Hello again!'
}
}
}
git log 将源代码存放目录里面的log目录上传到代码仓库中
git commit -am 'add log' git push
例子:(可copy)
cat Jenkinsfile
pipeline {
agent { label 'k8s-slave1'}
environment {
IMAGE_REPO = "1008611zmx/myblog"
DINGTALK_CREDS = credentials('dingtalk')
DOCKERHUB_CREDS = credentials('dockerhub')
TAB_STR = "\n \n "
}
stages {
stage('printenv') {
steps {
script{
sh "git log --oneline -n 1 > gitlog.file"
env.GIT_LOG = readFile("gitlog.file").trim()
}
sh 'printenv'
}
}
stage('checkout') {
steps {
checkout scm
script{
env.BUILD_TASKS = env.STAGE_NAME + "√..." + env.TAB_STR
}
}
}
stage('build-image') {
steps {
retry(2) { sh 'docker build . -t ${IMAGE_REPO}:${GIT_COMMIT}'}
script{
env.BUILD_TASKS += env.STAGE_NAME + "√..." + env.TAB_STR
}
}
}
stage('push-image') {
steps {
retry(2) { sh 'docker login -u ${DOCKERHUB_CREDS_USR} -p ${DOCKERHUB_CREDS_PSW}'}
retry(2) { sh 'docker push ${IMAGE_REPO}:${GIT_COMMIT}'}
script{
env.BUILD_TASKS += env.STAGE_NAME + "√..." + env.TAB_STR
}
}
}
stage('deploy') {
steps {
sh "sed -i 's#{{IMAGE_URL}}#${IMAGE_REPO}:${GIT_COMMIT}#g' deploy/*"
timeout(time: 1, unit: 'MINUTES') {
sh "kubectl apply -f deploy/"
}
script{
env.BUILD_TASKS += env.STAGE_NAME + "√..." + env.TAB_STR
}
}
}
}
post {
success {
echo 'Congratulations!'
sh """
curl 'https://oapi.dingtalk.com/robot/send?access_token=${DINGTALK_CREDS_PSW}' \
-H 'Content-Type: application/json' \
-d '{
"msgtype": "markdown",
"markdown": {
"title":"myblog",
"text": " 构建成功 \n**项目名称**:smile 33 \n**Git log**: ${GIT_LOG} \n**构建分支**: ${GIT_BRANCH} \n**构建地址**:${RUN_DISPLAY_URL} \n**构建任务**:${BUILD_TASKS}"
}
}'
"""
}
failure {
echo 'Oh no!'
sh """
curl 'https://oapi.dingtalk.com/robot/send?access_token=${DINGTALK_CREDS_PSW}' \
-H 'Content-Type: application/json' \
-d '{
"msgtype": "markdown",
"markdown": {
"title":"myblog",
"text": "❌ 构建失败 ❌ \n**项目名称**:smile \n**Git log**: ${GIT_LOG} \n**构建分支**: ${GIT_BRANCH} \n**构建地址**:${RUN_DISPLAY_URL} \n**构建任务**:${BUILD_TASKS}"
}
}'
"""
}
always {
echo 'I will always say Hello again!'
}
}
}
gitlab做代码合并 通知gitlab构建状态 Merge 代码合并
将下面的代码部分替换到上面写好的Jenkinsfile里面 57dd行
cat Jenkinsfile
pipeline {
agent { label 'k8s-slave1'}
options {
buildDiscarder(logRotator(numToKeepStr: '10'))
disableConcurrentBuilds()
timeout(time: 20, unit: 'MINUTES')
gitLabConnection('10.99.69.119') #定义三个参数 多分支为代码合并做准备 连接gitlab让我们在gitlab中看到jenkins流水线执行的状态
}
environment {
IMAGE_REPO = "1008611zmx/myblog"
DINGTALK_CREDS = credentials('dingtalk')
DOCKERHUB_CREDS = credentials('dockerhub')
TAB_STR = "\n \n "
}
stages {
stage('printenv') {
steps {
script{
sh "git log --oneline -n 1 > gitlog.file"
env.GIT_LOG = readFile("gitlog.file").trim()
}
sh 'printenv'
}
}
stage('checkout') {
steps {
checkout scm
updateGitlabCommitStatus(name: env.STAGE_NAME, state: 'success') #向gitlab发送流水线执行状态
script{
env.BUILD_TASKS = env.STAGE_NAME + "√..." + env.TAB_STR #钉钉的消息通知 如果checkout执行成功就写一个这个,向钉钉发送消息
}
}
}
stage('build-image') {
steps {
retry(2) { sh 'docker build . -t ${IMAGE_REPO}:${GIT_COMMIT}'}
updateGitlabCommitStatus(name: env.STAGE_NAME, state: 'success')
script{
env.BUILD_TASKS += env.STAGE_NAME + "√..." + env.TAB_STR
}
}
}
stage('push-image') {
steps {
retry(2) { sh 'docker login -u ${DOCKERHUB_CREDS_USR} -p ${DOCKERHUB_CREDS_PSW}'}
retry(2) { sh 'docker push ${IMAGE_REPO}:${GIT_COMMIT}'}
updateGitlabCommitStatus(name: env.STAGE_NAME, state: 'success')
script{
env.BUILD_TASKS += env.STAGE_NAME + "√..." + env.TAB_STR
}
}
}
stage('deploy') {
steps {
sh "sed -i 's#{{IMAGE_URL}}#${IMAGE_REPO}:${GIT_COMMIT}#g' deploy/*"
timeout(time: 1, unit: 'MINUTES') {
sh "kubectl apply -f deploy/"
}
updateGitlabCommitStatus(name: env.STAGE_NAME, state: 'success')
script{
env.BUILD_TASKS += env.STAGE_NAME + "√..." + env.TAB_STR
}
}
}
}
post {
success {
echo 'Congratulations!'
sh """
curl 'https://oapi.dingtalk.com/robot/send?access_token=${DINGTALK_CREDS_PSW}' \
-H 'Content-Type: application/json' \
-d '{
"msgtype": "markdown",
"markdown": {
"title":"myblog",
"text": " 构建成功 \n**项目名称**:smile 33 \n**Git log**: ${GIT_LOG} \n**构建分支**: ${GIT_BRANCH} \n**构建地址**:${RUN_DISPLAY_URL} \n**构建任务**:${BUILD_TASKS}"
}
}'
"""
}
failure {
echo 'Oh no!'
sh """
curl 'https://oapi.dingtalk.com/robot/send?access_token=${DINGTALK_CREDS_PSW}' \
-H 'Content-Type: application/json' \
-d '{
"msgtype": "markdown",
"markdown": {
"title":"myblog",
"text": "❌ 构建失败 ❌ \n**项目名称**:smile \n**Git log**: ${GIT_LOG} \n**构建分支**: ${GIT_BRANCH} \n**构建地址**:${RUN_DISPLAY_URL} \n**构建任务**:${BUILD_TASKS}"
}
}'
"""
}
always {
echo 'I will always say Hello again!'
}
}
}
由于我们配置了gitlab的互联 通知gitlab的构建状态 可以在gitlab commit里面看到完整的gitlab构建是否成功 每个构建步骤都可以从这里跳转到jenkins的图形界面 确定运行中没有问题 ,但是这个仅仅至少依据,不能确保代码是否真的有问题 可以作为代码合并的依据
把deploy yaml文件添加命名空间 把所有的服务和代码在命名空间下运行 让后隔离测试 ,让后把代码合并到master就可以更新了
现在基与整个jenkins不管是单/多流水线,还是自由构建的项目,首先要依赖于agent (标签代理)
我们在jenkins上配置了从节点, 将任务都尽量的在从节点上部署,那是因为master节点只要一出问题那麽就会导致整个jenkins服务的崩坏,但是要想在从节点上部署工作上的服务进行测试或者生产 ,就要给从节点master权限,但是在这样在k8s集群中是一种高度风险的操作(高危),就算我们可以单独在集群中拿出两道三台的从节点来运行任务 但是还是会带来一个新的问题,流水线不是24小时都会有流水的,只有当代码发生了更新之后才会有触发任务,当没有流水的时候机器也不可能一直的开着会造成资源的浪费 (造成平时没有代码更新的时候没有任何的流水线任务,但是一旦发送了代码更新之后就会连带这一大批的代码任务,就会可能造成节点不够用)
实现的目的:当我们需要流水线的时候我们就基与流水线的pod就创建出来,当不需要的时候就把pod资源删除
或者说:有代码更新触发流水线的时候,创建基与流水线的jenkins的pods,这个pod负责真个流水线的运行,当整个流水线任务完成后把jenkins的pod自动的删除 (前提条件是必须要和k8s集群对接)
好处:1.资源不会被浪费 2.就算很多时候一瞬间需要构建的任务或者一个节点承受不住的时候,也可以自动的创建出多个pods来抵挡 ,jenkins和k8s结合的很紧密
创建对接k8s下的pod模板
ps:如果工作空间卷选择的是host Path 本地存储的时候 就需要让pod设置成只运行在host path本地存储的节点上面才能读取数据
我们使用的是本地存储 在工作空间卷上面配置节点选择器 通过给所选的从节点上面打标签来实现
在master节点上面执行:kubectl label nodes master-node1 agent=true
同时在jenkins wed界面上面设置主机路径为绝对路径
并修改从节点的主机路径目录的权限chown -R 1000:1000 /opt/jenkins_jobs/
#是因为我们在创建jenkins pod的时候yaml将权限修改为了1000,如果不修改权限的话进来的时候直接就会挂root,那和我们的pod和jenkins的pod对连的时候直接就会报错!!
如果工作空间卷选择pvc的话,就需要创建一个pvc将pvc的值写在声明值里面
点击save #注意保存完成后将jenkinsfile脚本中的agent 修改为jnlp-slave 才能识别
修改完agent后 在源代码节点的目录中
git commit -am 'change slave to k8s pod'
git push
https://hub.docker.com/r/jenkins/inbound-agent
# git merge 代码合并
但是这样下载的jenkins-inbound-agent的镜像中没有docker命令,会报错!
解决方法: 1.直接在jenkins-inbound-agent镜像中安装docker命令
2.直接做两个pod
(在一个pod中搞两个容器 jenkins-inbound 作用:和jenkins来进行对接
tools 工具容器 作用:在这个容器中安装各种各样的工具
比如:tools-pyhon的容器环境 java-tools的容器环境等等 我们也可以直接把环境和docker命令等等的东西直接安装在jenkins-inbound但是这样的话就不够灵活 )
这里选择第二种:制作tools工具容器
在master节点 创建tools
mkdir tools
制作的tools工作容器中要有git命令 .kube/config 加入master权限 docker命令 和kubectl
which kubectl
/usr/bin/kubectl
[root@master-jenkins tools]# cp `which kubectl` .
[root@master-jenkins tools]# ls
kubectl
[root@master-jenkins tools]# cp /root/.kube/config .
[root@master-jenkins tools]# ls
config kubectl
[root@master-jenkins tools]# touch Dockerfile
# FROM alpine alpine的特点是小 run=apk
cat Dockerfile
FROM alpine
LABEL maintainer="1234567890.com"
USER root
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories && \
apk update && \
apk add --no-cache openrc docker git curl tar gcc g++ make \
bash shadow openjdk8 python2 python2-dev py-pip python3-dev openssl-dev libffi-dev \
libstdc++ harfbuzz nss freetype ttf-freefont chromium chromium-chromedriver && \
mkdir -p /root/.kube && \
usermod -a -G docker root
COPY config /root/.kube/
RUN rm -rf /var/cache/apk/*
#-----------------安装 kubectl--------------------#
COPY kubectl /usr/local/bin/
RUN chmod +x /usr/local/bin/kubectl
# ------------------------------------------------#
docker build -t 1008611zmx/tools:v1.0 . #docker push 1008611zmx/tools:v1.0 可以直接上传到工有镜像仓库中
在pod-template中添加 容器 Container Template
docker inspect jenkins/inbound-agent:3107.v665000b_51092-4 查看容器的详细信息
添加卷: 是因为tools这个容器想要运行必须调用docker.sock 安全套接字ll /var/run/docker.sock,要在添加卷的主机路径和挂载路径中挂载他
点击save
然后修改源代码仓库中的jenkinsfile,进行调用tools
cat Jenkinsfile
pipeline {
agent { label 'jnlp-slave'}
options {
buildDiscarder(logRotator(numToKeepStr: '10'))
disableConcurrentBuilds()
timeout(time: 20, unit: 'MINUTES')
gitLabConnection('10.99.69.119')
}
environment {
IMAGE_REPO = "1008611zmx/myblog"
DINGTALK_CREDS = credentials('dingtalk')
DOCKERHUB_CREDS = credentials('dockerhub')
TAB_STR = "\n \n "
}
stages {
stage('printenv') {
steps {
script{
sh "git log --oneline -n 1 > gitlog.file"
env.GIT_LOG = readFile("gitlog.file").trim()
}
sh 'printenv'
}
}
stage('checkout') {
steps {
container('tools') { #调用这个容器
checkout scm
}
updateGitlabCommitStatus(name: env.STAGE_NAME, state: 'success')
script{
env.BUILD_TASKS = env.STAGE_NAME + "√..." + env.TAB_STR
}
}
}
stage('build-image') {
steps {
container('tools') {
retry(2) { sh 'docker build . -t ${IMAGE_REPO}:${GIT_COMMIT}'}
}
updateGitlabCommitStatus(name: env.STAGE_NAME, state: 'success')
script{
env.BUILD_TASKS += env.STAGE_NAME + "√..." + env.TAB_STR
}
}
}
stage('push-image') {
steps {
container('tools') {
retry(2) { sh 'docker login -u ${DOCKERHUB_CREDS_USR} -p ${DOCKERHUB_CREDS_PSW}'}
retry(2) { sh 'docker push ${IMAGE_REPO}:${GIT_COMMIT}'}
}
updateGitlabCommitStatus(name: env.STAGE_NAME, state: 'success')
script{
env.BUILD_TASKS += env.STAGE_NAME + "√..." + env.TAB_STR
}
}
}
stage('deploy') {
steps {
container('tools') {
sh "sed -i 's#{{IMAGE_URL}}#${IMAGE_REPO}:${GIT_COMMIT}#g' deploy/*"
timeout(time: 1, unit: 'MINUTES') {
sh "kubectl apply -f deploy/"
}
}
updateGitlabCommitStatus(name: env.STAGE_NAME, state: 'success')
script{
env.BUILD_TASKS += env.STAGE_NAME + "√..." + env.TAB_STR
}
}
}
}
post {
success {
echo 'Congratulations!'
sh """
curl 'https://oapi.dingtalk.com/robot/send?access_token=${DINGTALK_CREDS_PSW}' \
-H 'Content-Type: application/json' \
-d '{
"msgtype": "markdown",
"markdown": {
"title":"myblog",
"text": " 构建成功 \n**项目名称**:smile 33 \n**Git log**: ${GIT_LOG} \n**构建分支**: ${GIT_BRANCH} \n**构建地址**:${RUN_DISPLAY_URL} \n**构建任务**:${BUILD_TASKS}"
}
}'
"""
}
failure {
echo 'Oh no!'
sh """
curl 'https://oapi.dingtalk.com/robot/send?access_token=${DINGTALK_CREDS_PSW}' \
-H 'Content-Type: application/json' \
-d '{
"msgtype": "markdown",
"markdown": {
"title":"myblog",
"text": "❌ 构建失败 ❌ \n**项目名称**:smile \n**Git log**: ${GIT_LOG} \n**构建分支**: ${GIT_BRANCH} \n**构建地址**:${RUN_DISPLAY_URL} \n**构建任务**:${BUILD_TASKS}"
}
}'
"""
}
always {
echo 'I will always say Hello again!'
}
}
}
git commit -am 'change slave to k8s pod'
至此具备小型工作的jenkins雏形已经具备
SonarQube 10.0 #merge 代码合并
jenkins: 在ci/cd中起到管理调度的作用
ps:为什么需要多分支流水线 :是因为在工作环境中不可能只有一个环境,而是至少有两个
测试环境和生产环境
代码提交之后的代码应该先往测试环境部署进行测试 (代码严格禁止在源代码上进行修改,修改源代码的复制品),如果测试环境的代码没有问题的话,在将其通过生产流水线,部署到生产环境中去,如果生产环境下的代码出现问题后,要修改测试环境的源代码的复制品进行更新,进行循环测试,当更新的代码没有问题后,把源代码复制器的更新,真正的源代码进行更新,进行合并,推送给生产环境的流水线进行部署!
ingress --金丝雀发布 --灰度发布 会对新版和旧版的流量进行一个分流 新版本抢先体验
Sonar可以从以下七个维度检测代码质量,而作为开发人员至少需要处理前5种代码质量问题。
1. 不遵循代码标准
sonar可以通过PMD,CheckStyle,Findbugs等等代码规则检测工具规范代码编写。
2. 潜在的缺陷
sonar可以通过PMD,CheckStyle,Findbugs等等代码规则检测工具检 测出潜在的缺陷。
3. 糟糕的复杂度分布
文件、类、方法等,如果复杂度过高将难以改变,这会使得开发人员 难以理解它们, 且如果没有自动化的单元测试,对于程序中的任何组件的改变都将可能导致需要全面的回归测试。
4. 重复
显然程序中包含大量复制粘贴的代码是质量低下的,sonar可以展示 源码中重复严重的地方。
5. 注释不足或者过多
没有注释将使代码可读性变差,特别是当不可避免地出现人员变动 时,程序的可读性将大幅下降 而过多的注释又会使得开发人员将精力过多地花费在阅读注释上,亦违背初衷。
6. 缺乏单元测试
sonar可以很方便地统计并展示单元测试覆盖率。
7. 糟糕的设计
通过sonar可以找出循环,展示包与包、类与类之间的相互依赖关系,可以检测自定义的架构规则 通过sonar可以管理第三方的jar包,可以利用LCOM4检测单个任务规则的应用情况, 检测耦合。
sonarqube架构简介
CS架构
sonarqube scanner
sonarqube server
SonarQube Scanner 扫描仪在本地执行代码扫描任务
执行完后,将分析报告被发送到SonarQube服务器进行处理
SonarQube服务器处理和存储分析报告导致SonarQube数据库,并显示结果在UI中
能够检测很多的代码 java python等等的
sonarqube on kubernetes环境搭建
资源文件准备
sonar/sonar.yaml
和gitlab共享postgres数据库
使用ingress地址 sonar.smile.com
进行访问
使用initContainers进行系统参数调整
安装sonarqube服务端
apiVersion: v1
kind: Service
metadata:
name: sonarqube-svc
namespace: jenkins
labels:
app: sonarqube
spec:
type: NodePort
ports:
- name: sonarqube
port: 9000
targetPort: 9000
protocol: TCP
nodePort: 32086
clusterIP: 10.107.89.235
selector:
app: sonarqube
---
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: jenkins
name: sonarqube
labels:
app: sonarqube
spec:
replicas: 1
selector:
matchLabels:
app: sonarqube
template:
metadata:
labels:
app: sonarqube
spec:
#nodeSelector:
# sonar: "true"
initContainers:
- command:
- /sbin/sysctl
- -w
- vm.max_map_count=262144 # 调整虚拟内存
image: alpine:3.6
imagePullPolicy: IfNotPresent
name: elasticsearch-logging-init
resources: {}
securityContext: #开启特权级 root权限 调整虚拟内存需要开启root模式
privileged: true
containers:
- name: sonarqube
image: sonarqube:7.9-community
ports:
- containerPort: 9000
env:
- name: SONARQUBE_JDBC_USERNAME
valueFrom:
secretKeyRef:
name: gitlab-secret2
key: username
- name: SONARQUBE_JDBC_PASSWORD
valueFrom:
secretKeyRef:
name: gitlab-secret2
key: password
- name: SONARQUBE_JDBC_URL
value: "jdbc:postgresql://10.97.235.67:5432/sonar" #5432 写的的postgresal的svc 的地址
livenessProbe:
httpGet:
path: /sessions/new
port: 9000
initialDelaySeconds: 60
periodSeconds: 30
readinessProbe:
httpGet:
path: /sessions/new
port: 9000
initialDelaySeconds: 60
periodSeconds: 30
failureThreshold: 6
resources:
limits:
cpu: 2000m
memory: 4096Mi
requests:
cpu: 100m
memory: 512Mi
volumeMounts:
- name: time-localtime
readOnly: true
mountPath: /etc/localtime
volumes:
- name: time-localtime
hostPath:
path: /etc/localtime
把代码质量检测写在Jenkins 流水线脚本里面的build-image前面,因为前面的checkout代码检测之后才能触发流水线的工作,让后才能扫描我们的代码,构建镜像,上传镜像 ,但是代码扫描解决不了所有问题只能解决一些基础问题比如:性能过多,注释不足,更多的是检测基础有没有问题,如果基础没有问题就上线进行测试,测试性能
️:因为用的云服务器所以如果服务连接不上或者报错的时候就检查以下host主机文件和k8s集群coreDns里面配置的静态路由!
在k8s集群中部署sonarqube服务端在jenkins命名空间下
因为我使用的是云服务器所以要修改yaml文件在使用
在执行此yaml文件的时候需要在postgres数据库中创建一个sonar数据库 !!!
kubectl exec -it -n jenkins postgres-statefulset-0 -- bash
create database sonar;
kubectl apply -f sonar.yaml
登录密码: 默认都是admin !
开发 专业 数据中心的插件需要付费 插件不是由sonar官方提供的 都是由社区或者国外提供的
我们安装部署了sonarqube-server服务端 同样还需要安装客户端 才能使用
通过客户端将scm检测之后触发jenkins触发器通过gitlab的wedhooks收取到更新的代码后通过sonarqube-client进行代码检测 ,之后将扫描的结果在发送给服务端 ,让后进行镜像构建 上传镜像等等
安装sonarqube-client客户端 (客户端和服务端都安装在master节点)
wget https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-4.2.0.1873-linux.zip
unzip sonar-scanner-cli-4.2.0.1873-linux.zip
修改配置文件 !
[root@master-jenkins sonar-scanner-4.2.0.1873-linux]# ls
bin#执行命令 conf#配置文件 jre#java包内置了java环境 支撑扫描脚本的java运行 lib#二进制文件
vim conf
#Configure here general information about the environment, such as SonarQube server connection details for example
#No information about specific project should appear here
#----- Default SonarQube server
#sonar.host.url=http://localhost:9000
sonar.host.url=http://10.107.89.235:9000 # 书写 svc的 地址 #如果是本地部署的话是应该写ingres域名 #注意如果本地部署就需要写host 和coredns里面配置静态路由
#----- Default source code encoding
#sonar.sourceEncoding=UTF-8
sonar bin下的执行脚本
[root@master-jenkins conf]# cd ../bin/
[root@master-jenkins bin]# ls
sonar-scanner sonar-scanner-debug #执行脚本 扫描代码!
如果想要执行代码扫描那麽应该在jenkins源代码的仓库目录里面执行脚本
../sonar-scanner-4.2.0.1873-linux/bin/sonar-scanner -X
执行../sonar-scanner-4.2.0.1873-linux/bin/sonar-scanner -X
报错: Caused by: You must define the following mandatory properties for 'Unknown': sonar.projectKey
原因: 缺乏sonar.projectKey 可以在官网中找到 https://docs.sonarqube.org/latest/analyzing-source-code/scanners/sonarscanner/
解决方法: 将**vim sonar-project.properties** 写到源代码仓库目录中 来定义扫描的内容
sonar.projectKey=myblog
sonar.projectName=myblog
# if you want disabled the DTD verification for a proxy problem for example, true by default
sonar.coverage.dtdVerification=false
# JUnit like test report, default value is test.xml
sonar.sources=blog,myblog #扫描的内容 blog,myblog代码在源代码放开目录中
让后在源代码仓库执行../sonar-scanner-4.2.0.1873-linux/bin/sonar-scanner -X
源代码仓库 ../sonar-scanner-4.2.0.1873-linux/bin/sonar-scanner -X
Total time: 5.740s #耗时 15:14:35.074 INFO: Final Memory: 12M/47M #使用了多少内存
到这,客户端扫描了之后就会把结果上传到服务端
:因为CI/CD的缘故我们希望将代码扫描检测sonar 集成到jenkins 代码仓库中!
:实现方法: 因为tools的镜像涵盖了很多的工具,如果我们想做一个具有代码检测功能的流水线,客户我们就需要把sonar-scanner-4.2.0.1873-linux端集成到我们jnlp的pod中,在我们的jnlp中pod中有两个容器,一个是jnlp容器 和jenkins来进行对接,一个是tools 工具容器 作用,根据两个容器的作用我们应该把sonar-scanner-4.2.0.1873-linux客户端集成放在tools容器中
因为我们sonar-scanner-4.2.0.1873-linux客户端中在jre中集成内置了java环境 而我们要把客户端集成到tools容器中而tools容器中已经有了java环境 为了减少我们的环境在集成过程中就不需要jre目录了 还有一点是因为在sonar-scanner-4.2.0.1873-linux客户端jre目录特别占存储
[root@master-jenkins tools]# cp -r ../sonar-scanner-4.2.0.1873-linux/ sonar-scanner
[root@master-jenkins tools]# ls
config Dockerfile kubectl sonar-scanner
[root@master-jenkins tools]# cd sonar-scanner/
[root@master-jenkins sonar-scanner]# ls
bin conf jre lib
[root@master-jenkins sonar-scanner]# rm -rf jre/
[root@master-jenkins sonar-scanner]# cd ..
[root@master-jenkins tools]# cd sonar-scanner/
[root@master-jenkins sonar-scanner]# cd bin/
[root@master-jenkins bin]# vim sonar-scanner
sonar-scanner sonar-scanner-debug
#use_embedded_jre=true 改为 use_embedded_jre=false embedded内置 因为我们我们tools应该有java环境我们不希望他使用自己内置的java环境所以要修改为false
然后将tools的Dockerfile重新书写
cat Dockerfile
FROM alpine
LABEL maintainer="[email protected]"
USER root
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories && \
apk update && \
apk add --no-cache openrc docker git curl tar gcc g++ make \
bash shadow openjdk8 python2 python2-dev py-pip python3-dev openssl-dev libffi-dev \
libstdc++ harfbuzz nss freetype ttf-freefont chromium chromium-chromedriver && \
mkdir -p /root/.kube && \
usermod -a -G docker root
COPY config /root/.kube/
RUN rm -rf /var/cache/apk/*
#-----------------安装 kubectl--------------------#
COPY kubectl /usr/local/bin/
RUN chmod +x /usr/local/bin/kubectl
# ------------------------------------------------#
#---------------安装 sonar-scanner-----------------#
COPY sonar-scanner /usr/lib/sonar-scanner
RUN ln -s /usr/lib/sonar-scanner/bin/sonar-scanner /usr/local/bin/sonar-scanner && chmod +x /usr/local/bin/sonar-scanner
ENV SONAR_RUNNER_HOME=/usr/lib/sonar-scanner
# ------------------------------------------------#
docker build -t 1008611zmx/tools:v2 .
让后docker login 登录公有镜像仓库
docker push 1008611zmx/tools:v2
让后把jenkins系统管理--节点管理---配置集群中的pod template的jnlp容器中的tools容器进行修改!修改为我们最新制作的tools容器的名称
然后将jenkins 连接sonar
在jenkins wed页面下载sonar的插件
当gitlab发生了代码更新之后 代码拉到我们jenkins-slave中jenkins-slave-pod(inbound tools两个容器)我们tools容器中集成了sonar-client 将代码的扫描结果上传给了sonar-server服务端,当扫描结果通过了之后,让后用代码制作镜像,把镜像推送给镜像仓库,因为我们jenkins对接了k8s,k8s向我们的镜像仓库进行访问拉取镜像 部署服务 其中jenkins扮演了一个指挥官 调度官的角色
下载安装完sonar插件之后我们需要创建一个属于自己的对接的工具,这个工具需要有一个自己的token凭证
#在jenkins wed页面 系统管理--创建凭证
而jenkins凭证里面需要的token值需要到sonar中创建
让后在系统管理中 SonarQube servers配置 进行对接
Server URL:如果是本地部署的那麽我们应该写ingress 因为ingress的域名是集群的内部
需要在kubectl edit -n kube-system cm coredns中配置静态路由 # 着急的话可以强制更新
sonar距拣门 (什么样的标准 代码才会允许通过) #相当于操作系统的防火墙!!!
什么样的标准 代码运行通过 (如果开发写的代码符合我们的标准就通过,如果没符合就不通过)
Metric Operator Error
Coverage on New Code is less than 80.0% #代码 修改/改变超过代码80%才能通过 小于百分之80报错
Duplicated Lines on New Code is greater than 3.0% #新代码的重复行大于百分之三 才通过
Maintainability Rating on New Code is worse than A
Reliability Rating on New Code is worse than A
Security Rating on New Code is worse than A
在sonar中创建规则 (因为默认的规则可能会不符合需要创建)
创建出新的规则后如何将项目调进来:通过以下方式就可以
让后在规则中创建策略来对All选中的项目进行使用
让后将实例apply上:#将测试yaml文件先执行上 让后通过流水线进行更新 更新的时候只需要更新myblog!!!
cat deploy-mysql.yaml
apiVersion: v1
kind: Service
metadata:
name: mysql-svc
namespace: develop
spec:
ports:
- port: 3306
protocol: TCP
targetPort: 3306
selector:
app: mysql
#type: ClusterIP
clusterIP: 10.104.238.30
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
namespace: develop
spec:
replicas: 1 #指定Pod副本数
selector: #指定Pod的选择器
matchLabels:
app: mysql
template:
metadata:
labels: #给Pod打label
app: mysql
spec:
containers:
- name: mysql
image: mysql:5.7
ports:
- containerPort: 3306
env:
#- name: MYSQL_USER
# #value: root
- name: MYSQL_ROOT_PASSWORD
value: "123456"
- name: MYSQL_DATABASE
value: "myblog"
resources:
requests:
memory: 100Mi
cpu: 50m
limits:
memory: 500Mi
cpu: 100m
cat /myblog/deploy-myblog.yaml
apiVersion: v1
kind: Service
metadata:
name: myblog-svc
labels:
app: myblog
namespace: develop
spec:
type: NodePort
ports:
- name: myblog
port: 8002
targetPort: 8002
protocol: TCP
nodePort: 32087
clusterIP: 10.104.222.190
selector:
app: myblog
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: myblog
namespace: develop
spec:
replicas: 1 #指定Pod副本数
selector: #指定Pod的选择器
matchLabels:
app: myblog
template:
metadata:
labels: #给Pod打label
app: myblog
spec:
containers:
- name: myblog
image: smsmcopy/myblog:627c60d6727d617f78b4ca7cc1d1191f91def301
imagePullPolicy: IfNotPresent
env:
- name: MYSQL_HOST
value: "10.104.238.30"
- name: MYSQL_PORT
value: "3306"
- name: MYSQL_USER
value: "root"
- name: MYSQL_PASSWD
value: "123456"
ports:
- containerPort: 8002
resources:
requests:
memory: 100Mi
cpu: 50m
limits:
memory: 500Mi
cpu: 100m
kubectl exec -it -n develop myblog-67976548d7-k58ln -- bash
myblog有一个典型的python文件需要执行 manage.py
python3 manage.py makemigrations #如果失败请先检测pod的与svc直接是否正常连接 lable
python3 manage.py migrate
python3 manage.py createsuperuser #生成一个管理员密码 也是myblog的登录密码
至此一个简略的博客完成
http://123.60.162.77:32087/blog/article/edit/0
将源代码仓库中的deploy目录里面的myblog的yaml文件进行修改 也可以直接替换
cp /myblog/deploy-myblog.yaml .
部署完sonar代码扫描之后和myblog的博客之后对源代码目录中的Jenkins流水线脚本进行修改
因为在源代码仓库执行../sonar-scanner-4.2.0.1873-linux/bin/sonar-scanner -X 所以要在Jenkins中进行调用
在jenkins中介绍了如何在流水线脚本中写pipeline SonarQube Scanner for Jenkins
把sonar的脚本写并放在Jenkinsfile流水线脚本scm后面
cat Jenkinsfile
pipeline {
agent { label 'jnlp-slave'}
options {
buildDiscarder(logRotator(numToKeepStr: '10'))
disableConcurrentBuilds()
timeout(time: 20, unit: 'MINUTES')
gitLabConnection('10.99.69.119')
}
environment {
IMAGE_REPO = "1008611zmx/myblog"
DINGTALK_CREDS = credentials('dingtalk')
DOCKERHUB_CREDS = credentials('dockerhub')
TAB_STR = "\n \n "
}
stages {
stage('printenv') {
steps {
script{
sh "git log --oneline -n 1 > gitlog.file"
env.GIT_LOG = readFile("gitlog.file").trim()
}
sh 'printenv'
}
}
stage('checkout') {
steps {
container('tools') {
checkout scm
}
updateGitlabCommitStatus(name: env.STAGE_NAME, state: 'success')
script{
env.BUILD_TASKS = env.STAGE_NAME + "√..." + env.TAB_STR
}
}
}
stage('CI'){
failFast true #只要失败立刻终止
parallel { #并行参数 (因为我们的steps是一步一步的进行的 但是加了paraller参数之后 stage可以同时运行)
stage('Unit Test(单元测试)') {
steps {
echo "Unit Test Stage Skip..."
}
}
stage('Code Scan') { #代码扫描
steps {
container('tools(调用tools容器)') {
withSonarQubeEnv('sonarqube(是我们的sonarqube的svc地址#如果写svc地址错误之后 替换成sonarqube的全局唯一凭证)') {
sh 'sonar-scanner -X' #调用我们代码扫描的程序在源代码目录里面进行扫描 (之前客户端的执行脚本命令)
sleep 10
}
script {
timeout(1) {
def qg = waitForQualityGate('sonarqube(是我们的sonarqube的svc地址#如果写svc地址错误之后 替换成sonarqube的全局唯一凭证)') #def 函数 调用 sonarqube变量 如果qg.status不ok的话就报一个错 配合failFast true 立刻停止
if (qg.status != 'OK') {
error "未通过Sonarqube的代码质量阈检查,请及时修改!failure: ${qg.status}"
}
}
}
}
}
}
}
}
stage('build-image') {
steps {
container('tools') {
retry(2) { sh 'docker build . -t ${IMAGE_REPO}:${GIT_COMMIT}'}
}
updateGitlabCommitStatus(name: env.STAGE_NAME, state: 'success')
script{
env.BUILD_TASKS += env.STAGE_NAME + "√..." + env.TAB_STR
}
}
}
stage('push-image') {
steps {
container('tools') {
retry(2) { sh 'docker login -u ${DOCKERHUB_CREDS_USR} -p ${DOCKERHUB_CREDS_PSW}'}
retry(2) { sh 'docker push ${IMAGE_REPO}:${GIT_COMMIT}'}
}
updateGitlabCommitStatus(name: env.STAGE_NAME, state: 'success')
script{
env.BUILD_TASKS += env.STAGE_NAME + "√..." + env.TAB_STR
}
}
}
stage('deploy') {
steps {
container('tools') {
sh "sed -i 's#{{IMAGE_URL}}#${IMAGE_REPO}:${GIT_COMMIT}#g' deploy/*"
timeout(time: 1, unit: 'MINUTES') {
sh "kubectl apply -f deploy/"
}
}
updateGitlabCommitStatus(name: env.STAGE_NAME, state: 'success')
script{
env.BUILD_TASKS += env.STAGE_NAME + "√..." + env.TAB_STR
}
}
}
}
post {
success {
echo 'Congratulations!'
sh """
curl 'https://oapi.dingtalk.com/robot/send?access_token=${DINGTALK_CREDS_PSW}' \
-H 'Content-Type: application/json' \
-d '{
"msgtype": "markdown",
"markdown": {
"title":"myblog",
"text": " 构建成功 \n**项目名称**:smile 33 \n**Git log**: ${GIT_LOG} \n**构建分支**: ${GIT_BRANCH} \n**构建地址**:${RUN_DISPLAY_URL} \n**构建任务**:${BUILD_TASKS}"
}
}'
"""
}
failure {
echo 'Oh no!'
sh """
curl 'https://oapi.dingtalk.com/robot/send?access_token=${DINGTALK_CREDS_PSW}' \
-H 'Content-Type: application/json' \
-d '{
"msgtype": "markdown",
"markdown": {
"title":"myblog",
"text": "❌ 构建失败 ❌ \n**项目名称**:smile \n**Git log**: ${GIT_LOG} \n**构建分支**: ${GIT_BRANCH} \n**构建地址**:${RUN_DISPLAY_URL} \n**构建任务**:${BUILD_TASKS}"
}
}'
"""
}
always {
echo 'I will always say Hello again!'
}
}
}
[root@master-jenkins python-demo-master]# git branch
* develop
master
因为内我们deploy改变了 sonar-project.properties 又新增了一个目录(是我们执行sonar客户端执行出来的)
[root@master-jenkins python-demo-master]# git add .
[root@master-jenkins python-demo-master]# git commit -am 'add sonarqube'
[root@master-jenkins python-demo-master]# git push
git add --all
git status
git comtios -am "updata Jenkins"
git push
如果报错就将我们之前手动执行的代码检测生成的.sonarqube删除就可以了
如果报错的话,将当时我们用命令执行出来的.scannerwork 进行删除 !!!
那麽做到这里请严格注意凭证的全局唯一id名字!
集成RobotFramework实现验收测试
一个基于Python语言,用于验收测试和验收测试驱动开发(ATDD)的通用测试自动化框架,提供了一套特定的语法,并且有非常丰富的测试库 。
robot用例简介 #在下面启动tools容器做验收测试的时候写入进去 vi的方式
robot/robot.txt
*** Settings ***
Library RequestsLibrary
Library SeleniumLibrary
*** Variables ***
${demo_url} http://myblog.smile.com/admin
*** Test Cases ***
api
[Tags] critical
Create Session api ${demo_url}
${alarm_system_info} RequestsLibrary.Get Request api /
log ${alarm_system_info.status_code}
log ${alarm_system_info.content}
should be true ${alarm_system_info.status_code} == 200
# 使用tools镜像启动容器,来验证手动使用robotframework来做验收测试 #在v2容器中
$ docker run --rm -ti smsmcopy/tools:v2 bash
bash-5.0# apk add chromium chromium-chromedriver
$ vi requirements.txt
robotframework
robotframework-seleniumlibrary
robotframework-databaselibrary
robotframework-requests
#pip安装必要的软件包
$ pip install -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com -r requirements.txt
#使用robot命令做测试
$ robot -d artifacts/ robot.txt
FROM alpine
LABEL maintainer="[email protected]"
USER root
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories && \
apk update && \
apk add --no-cache openrc docker git curl tar gcc g++ make \
bash shadow openjdk8 python2 python2-dev py-pip python3-dev openssl-dev libffi-dev \
libstdc++ harfbuzz nss freetype ttf-freefont chromium chromium-chromedriver && \
mkdir -p /root/.kube && \
usermod -a -G docker root
COPY config /root/.kube/
COPY requirements.txt /
RUN pip install -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com -r requirements.txt
RUN rm -rf /var/cache/apk/* && \
rm -rf ~/.cache/pip
#-----------------安装 kubectl--------------------#
COPY kubectl /usr/local/bin/
RUN chmod +x /usr/local/bin/kubectl
# ------------------------------------------------#
#---------------安装 sonar-scanner-----------------#
COPY sonar-scanner /usr/lib/sonar-scanner
RUN ln -s /usr/lib/sonar-scanner/bin/sonar-scanner /usr/local/bin/sonar-scanner && chmod +x /usr/local/bin/sonar-scanner
ENV SONAR_RUNNER_HOME=/usr/lib/sonar-scanner
# ------------------------------------------------#
在构建镜像之前将requirements.txt 写入到tools目录中
vi requirements.txt
robotframework
robotframework-seleniumlibrary
robotframework-databaselibrary
robotframework-requests
docker build -t 1008611zmx/tools:v3 .
docker push 1008611zmx/tools:v3
让后 更新Jenkins wed界面中kubernetes中的containers template tools版本
安装插件:
安装robotFramework
插件中心搜索robotframework,直接安装
在源代码仓库添加 robbot.txt
cat robbot.txt
*** Settings ***
Library RequestsLibrary
Library SeleniumLibrary
*** Variables ***
${demo_url} http://10.98.118.196/admin #修改为myblog的svc
*** Test Cases ***
api
[Tags] critical
Create Session api ${demo_url}
${alarm_system_info} RequestsLibrary.Get Request api /
log ${alarm_system_info.status_code}
log ${alarm_system_info.content}
should be true ${alarm_system_info.status_code} == 200
python-demo项目添加robot.txt文件:
jenkins/pipelines/p10.yaml
pipeline {
agent { label 'jnlp-slave'}
options {
buildDiscarder(logRotator(numToKeepStr: '10'))
disableConcurrentBuilds()
timeout(time: 20, unit: 'MINUTES')
gitLabConnection('gitlab')
}
environment {
IMAGE_REPO = "smsmcopay/myblog"
DINGTALK_CREDS = credentials('dingtalk')
DOCKERHUB_CREDS = credentials('dockerhub')
TAB_STR = "\n \n "
}
stages {
stage('printenv') {
steps {
script{
sh "git log --oneline -n 1 > gitlog.file"
env.GIT_LOG = readFile("gitlog.file").trim()
}
sh 'printenv'
}
}
stage('checkout') {
steps {
container('tools') {
checkout scm
}
updateGitlabCommitStatus(name: env.STAGE_NAME, state: 'success')
script{
env.BUILD_TASKS = env.STAGE_NAME + "√..." + env.TAB_STR
}
}
}
stage('build-image') {
steps {
container('tools') {
retry(2) { sh 'docker build . -t ${IMAGE_REPO}:${GIT_COMMIT}'}
}
updateGitlabCommitStatus(name: env.STAGE_NAME, state: 'success')
script{
env.BUILD_TASKS += env.STAGE_NAME + "√..." + env.TAB_STR
}
}
}
stage('push-image') {
steps {
container('tools') {
retry(2) { sh 'docker login -u ${DOCKERHUB_CREDS_USR} -p ${DOCKERHUB_CREDS_PSW}'}
retry(2) { sh 'docker push ${IMAGE_REPO}:${GIT_COMMIT}'}
}
updateGitlabCommitStatus(name: env.STAGE_NAME, state: 'success')
script{
env.BUILD_TASKS += env.STAGE_NAME + "√..." + env.TAB_STR
}
}
}
stage('deploy') {
steps {
container('tools') {
sh "sed -i 's#{{IMAGE_URL}}#${IMAGE_REPO}:${GIT_COMMIT}#g' deploy/*"
timeout(time: 1, unit: 'MINUTES') {
sh "kubectl apply -f deploy/"
}
}
updateGitlabCommitStatus(name: env.STAGE_NAME, state: 'success')
script{
env.BUILD_TASKS += env.STAGE_NAME + "√..." + env.TAB_STR
}
}
}
stage('Accept Test') {
steps {
container('tools') {
sh 'robot -i critical -d artifacts/ robot.txt|| echo ok'
echo "R ${currentBuild.result}"
step([
$class : 'RobotPublisher',
outputPath: 'artifacts/',
outputFileName : "output.xml",
disableArchiveOutput : false,
passThreshold : 80,
unstableThreshold: 20.0,
onlyCritical : true,
otherFiles : "*.png"
])
echo "R ${currentBuild.result}"
archiveArtifacts artifacts: 'artifacts/*', fingerprint: true
}
}
}
}
post {
success {
echo 'Congratulations!'
sh """
curl 'https://oapi.dingtalk.com/robot/send?access_token=${DINGTALK_CREDS_PSW}' \
-H 'Content-Type: application/json' \
-d '{
"msgtype": "markdown",
"markdown": {
"title":"myblog",
"text": " 构建成功 \n**项目名称**:smile \n**Git log**: ${GIT_LOG} \n**构建分支**: ${GIT_BRANCH} \n**构建地址**:${RUN_DISPLAY_URL} \n**构建任务**:${BUILD_TASKS}"
}
}'
"""
}
failure {
echo 'Oh no!'
sh """
curl 'https://oapi.dingtalk.com/robot/send?access_token=${DINGTALK_CREDS_PSW}' \
-H 'Content-Type: application/json' \
-d '{
"msgtype": "markdown",
"markdown": {
"title":"myblog",
"text": "❌ 构建失败 ❌ \n**项目名称**:smile \n**Git log**: ${GIT_LOG} \n**构建分支**: ${GIT_BRANCH} \n**构建地址**:${RUN_DISPLAY_URL} \n**构建任务**:${BUILD_TASKS}"
}
}'
"""
}
always {
echo 'I will always say Hello again!'
}
}
}
在Jenkins中查看robot的构建结果。
cat Jenkinsfile
pipeline {
agent { label 'jnlp-slave'}
options {
buildDiscarder(logRotator(numToKeepStr: '10'))
disableConcurrentBuilds()
timeout(time: 20, unit: 'MINUTES')
gitLabConnection('10.99.69.119')
}
environment {
IMAGE_REPO = "1008611zmx/myblog"
DINGTALK_CREDS = credentials('dingtalk')
DOCKERHUB_CREDS = credentials('dockerhub')
TAB_STR = "\n \n "
}
stages {
stage('printenv') {
steps {
script{
sh "git log --oneline -n 1 > gitlog.file"
env.GIT_LOG = readFile("gitlog.file").trim()
}
sh 'printenv'
}
}
stage('checkout') {
steps {
container('tools') {
checkout scm
}
updateGitlabCommitStatus(name: env.STAGE_NAME, state: 'success')
script{
env.BUILD_TASKS = env.STAGE_NAME + "√..." + env.TAB_STR
}
}
}
stage('CI'){
failFast true
parallel {
stage('Unit Test') {
steps {
echo "Unit Test Stage Skip..."
}
}
stage('Code Scan') {
steps {
container('tools') {
withSonarQubeEnv('sonarqube') {
sh 'sonar-scanner -X'
sleep 10
}
script {
timeout(1) {
def qg = waitForQualityGate('sonarqube')
if (qg.status != 'OK') {
error "未通过Sonarqube的代码质量阈检查,请及时修改!failure: ${qg.status}"
}
}
}
}
}
}
}
}
stage('build-image') {
steps {
container('tools') {
retry(2) { sh 'docker build . -t ${IMAGE_REPO}:${GIT_COMMIT}'}
}
updateGitlabCommitStatus(name: env.STAGE_NAME, state: 'success')
script{
env.BUILD_TASKS += env.STAGE_NAME + "√..." + env.TAB_STR
}
}
}
stage('push-image') {
steps {
container('tools') {
retry(2) { sh 'docker login -u ${DOCKERHUB_CREDS_USR} -p ${DOCKERHUB_CREDS_PSW}'}
retry(2) { sh 'docker push ${IMAGE_REPO}:${GIT_COMMIT}'}
}
updateGitlabCommitStatus(name: env.STAGE_NAME, state: 'success')
script{
env.BUILD_TASKS += env.STAGE_NAME + "√..." + env.TAB_STR
}
}
}
stage('deploy') {
steps {
container('tools') {
sh "sed -i 's#{{IMAGE_URL}}#${IMAGE_REPO}:${GIT_COMMIT}#g' deploy/*"
timeout(time: 1, unit: 'MINUTES') {
sh "kubectl apply -f deploy/"
}
}
updateGitlabCommitStatus(name: env.STAGE_NAME, state: 'success')
script{
env.BUILD_TASKS += env.STAGE_NAME + "√..." + env.TAB_STR
}
}
}
stage('Accept Test') {
steps {
container('tools') {
sh 'robot -i critical -d artifacts/ robot.txt|| echo ok'
echo "R ${currentBuild.result}"
step([
$class : 'RobotPublisher',
outputPath: 'artifacts/',
outputFileName : "output.xml",
disableArchiveOutput : false,
passThreshold : 80,
unstableThreshold: 20.0,
onlyCritical : true,
otherFiles : "*.png"
])
echo "R ${currentBuild.result}"
archiveArtifacts artifacts: 'artifacts/*', fingerprint: true
}
}
}
}
post {
success {
echo 'Congratulations!'
sh """
curl 'https://oapi.dingtalk.com/robot/send?access_token=${DINGTALK_CREDS_PSW}' \
-H 'Content-Type: application/json' \
-d '{
"msgtype": "markdown",
"markdown": {
"title":"myblog",
"text": " 构建成功 \n**项目名称**:smile \n**Git log**: ${GIT_LOG} \n**构建分支**: ${GIT_BRANCH} \n**构建地址**:${RUN_DISPLAY_URL} \n**构建任务**:${BUILD_TASKS}"
}
}'
"""
}
failure {
echo 'Oh no!'
sh """
curl 'https://oapi.dingtalk.com/robot/send?access_token=${DINGTALK_CREDS_PSW}' \
-H 'Content-Type: application/json' \
-d '{
"msgtype": "markdown",
"markdown": {
"title":"myblog",
"text": "❌ 构建失败 ❌ \n**项目名称**:smile \n**Git log**: ${GIT_LOG} \n**构建分支**: ${GIT_BRANCH} \n**构建地址**:${RUN_DISPLAY_URL} \n**构建任务**:${BUILD_TASKS}"
}
}'
"""
}
always {
echo 'I will always say Hello again!'
}
}
}
小结
思路:
讲解最基础的Jenkins的使用
Pipeline流水线的使用
Jenkinsfile的使用
多分支流水线的使用
与Kubernetes集成,动态jnlp slave pod的使用
与sonarqube集成,实现代码扫描
与Robotframework集成,实现验收测试
问题:
Jenkinsfile过于冗长
多个项目配置Jenkinsfile,存在很多重复内容
没有实现根据不同分支来部署到不同的环境
Java项目的构建
k8s部署后,采用等待的方式执行后续步骤,不合理