基于K8s的DevOps平台实践(三)
阿里云国内75折 回扣 微信号:monov8 |
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6 |
文章目录
前言
今天是「基于K8s的DevOps平台实践」的最后一篇前两篇地址如下
上面两篇均排在云原生领域榜第一
1. Jenkins与k8s集成
- Jenkins 如何对接 kubernetes 集群
- 使用 kubernetes 的 Pod-Template 来作为动态的 agent 执行 Jenkins 任务
- 如何制作 agent 容器实现不同类型的业务的集成
- 集成代码扫描、docker 镜像自动构建、k8s 服务部署、自动化测试
🍑 插件安装及配置
-
[系统管理] -> [插件管理] -> [搜索kubernetes] -> 直接安装
若安装失败请先更新 bouncycastle API Plugin 并重新启动Jenkins
-
[系统管理] -> [系统配置] -> [Add a new cloud]
-
配置地址信息
- Kubernetes 地址
https://kubernetes.default
- Kubernetes 命名空间jenkins
- 服务证书不用写我们在安装 Jenkins 的时候已经指定过 serviceAccount均使用默认
- 连接测试成功会提示Connection test successful
- Jenkins地址
http://jenkins:8080
- Jenkins 通道
jenkins:50000
- Kubernetes 地址
-
配置 Pod Template
-
名称jnlp-slave
-
命名空间jenkins
-
标签列表jnlp-slave作为 agent 的 label 选择用
-
连接 Jenkins 的超时时间秒300设置连接 jenkins 超时时间
-
工作空间卷选择 hostpath设置
/opt/jenkins
注意需要设置目录权限否则 Pod 没有权限
-
$ chown -R 1000:1000 /opt/jenkins
$ chmod 700 /opt/jenkins
🍑 演示动态slave pod
# 为准备运行jnlp-slave-agent的pod的节点打上label
$ kubectl label node k8s-slave1 agent=true
### 回放一次多分支流水线develop分支
agent { label 'jnlp-slave'}
执行任务会下载默认的 jnlp-slave 镜像地址为 jenkins/inbound-agent:4.3-4
我们可以先在 k8s-master 节点拉取下来该镜像
$ docker pull jenkins/inbound-agent:4.3-4
保存 jenkinsfile 提交后会出现报错因为我们的 agent 已经不再是宿主机而是 Pod 中的容器内报错如下
因此我们需要将用到的命令行工具集成到 Pod 的容器内但是思考如下问题
- 目前是用的 jnlp 的容器是 java 的环境我们在此基础上需要集成很多工具能不能创建一个新的容器让新容器来做具体的任务jnlp-slave 容器只用来负责连接 jenkins-master
- 针对不同的构建环境java、python、go、nodejs可以制作不同的容器来执行对应的任务
🍑 Pod-Template中容器镜像的制作
为解决上述问题我们制作一个 tools 镜像集成常用的工具来完成常见的构建任务需要注意的几点
- 使用 alpine 基础镜像自身体积比较小
- 替换国内安装源
- 为了使用 docker安装了 docker
- 为了克隆代码安装 git
- 为了后续做 python 的测试等任务安装 python 环境
- 为了在容器中调用 kubectl 的命令拷贝了 kubectl 的二进制文件
- 为了认证 kubectl需要在容器内部生成
.kube
目录及config
文件
$ mkdir tools;
$ cd tools;
$ cp `which kubectl` .
$ cp ~/.kube/config .
Dockerfile
jenkins/custom-images/tools/Dockerfile
FROM alpine:3.13.4
LABEL maintainer="inspur_lyx@hotmail.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 && \
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 172.21.51.143:5000/devops/tools:v1
$ docker push 172.21.51.143:5000/devops/tools:v1
我们可以直接使用该镜像做测试
## 启动临时镜像做测试
$ docker run --rm -ti 172.21.51.143:5000/devops/tools:v1 bash
# / git clone http://xxxxxx.git
# / kubectl get no
# / python3
#/ docker
## 重新挂载docker的sock文件
docker run -v /var/run/docker.sock:/var/run/docker.sock --rm -ti 172.21.51.143:5000/devops/tools:v1 bash
🍑 实践通过Jenkinsfile实现demo项目自动发布到kubenetes环境
实践通过 Jenkinsfile 实现 demo 项目自动发布到 kubenetes 环境
在卷栏目添加卷Host Path Volume不然在容器中使用 docker 会提示 docker 服务未启动
tools 容器做好后我们需要对 Jenkinsfile 做如下调整
jenkins/pipelines/p8.yaml
pipeline {
agent { label 'jnlp-slave'}
options {
buildDiscarder(logRotator(numToKeepStr: '10'))
disableConcurrentBuilds()
timeout(time: 20, unit: 'MINUTES')
gitLabConnection('gitlab')
}
environment {
IMAGE_REPO = "172.21.51.143:5000/myblog"
DINGTALK_CREDS = credentials('dingTalk')
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 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' manifests/*"
timeout(time: 1, unit: 'MINUTES') {
sh "kubectl apply -f manifests/"
}
}
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**项目名称**luffy \n**Git log**: ${GIT_LOG} \n**构建分支**: ${BRANCH_NAME} \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**项目名称**luffy \n**Git log**: ${GIT_LOG} \n**构建分支**: ${BRANCH_NAME} \n**构建地址**${RUN_DISPLAY_URL} \n**构建任务**${BUILD_TASKS}"
}
}'
"""
}
always {
echo 'I will always say Hello again!'
}
}
}
2. Jenkins集成Sonarqube
集成 sonarQube 实现代码扫描
Sonar 可以从以下七个维度检测代码质量而作为开发人员至少需要处理前 5 种代码质量问题。
- 不遵循代码标准
sonar 可以通过 PMD、CheckStyle、Findbugs 等等代码规则检测工具规范代码编写。 - 潜在的缺陷
sonar 可以通过 PMD、CheckStyle、Findbugs 等等代码规则检测工具检 测出潜在的缺陷。 - 糟糕的复杂度分布
文件、类、方法等如果复杂度过高将难以改变这会使得开发人员 难以理解它们, 且如果没有自动化的单元测试对于程序中的任何组件的改变都将可能导致需要全面的回归测试。 - 重复
显然程序中包含大量复制粘贴的代码是质量低下的sonar 可以展示 源码中重复严重的地方。 - 注释不足或者过多
没有注释将使代码可读性变差特别是当不可避免地出现人员变动时程序的可读性将大幅下降而过多的注释又会使得开发人员将精力过多地花费在阅读注释上亦违背初衷。 - 缺乏单元测试
sonar 可以很方便地统计并展示单元测试覆盖率。 - 糟糕的设计
通过 sonar 可以找出循环展示包与包、类与类之间的相互依赖关系可以检测自定义的架构规则通过 sonar 可以管理第三方的 jar 包可以利用 LCOM4 检测单个任务规则的应用情况检测耦合。
🍑 sonarqube架构简介
如图所示
- CS 架构
- sonarqube scanner
- sonarqube server
- SonarQube Scanner 扫描仪在本地执行代码扫描任务
- 执行完后将分析报告被发送到 SonarQube 服务器进行处理
- SonarQube 服务器处理和存储分析报告导致 SonarQube 数据库并显示结果在 UI 中
🍑 sonarqube on kubernetes环境搭建
- 资源文件准备
sonar/sonar.yaml
- 和 gitlab 共享 postgres 数据库
- 使用 ingress 地址
sonar.luffy.com
进行访问 - 使用 initContainers 进行系统参数调整
apiVersion: v1
kind: Service
metadata:
name: sonarqube
namespace: jenkins
labels:
app: sonarqube
spec:
ports:
- name: sonarqube
port: 9000
targetPort: 9000
protocol: TCP
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:
initContainers:
- command:
- /sbin/sysctl
- -w
- vm.max_map_count=262144
image: alpine:3.6
imagePullPolicy: IfNotPresent
name: elasticsearch-logging-init
resources: {}
securityContext:
privileged: true
containers:
- name: sonarqube
image: sonarqube:7.9-community
ports:
- containerPort: 9000
env:
- name: SONARQUBE_JDBC_USERNAME
valueFrom:
secretKeyRef:
name: gitlab-secret
key: postgres.user.root
- name: SONARQUBE_JDBC_PASSWORD
valueFrom:
secretKeyRef:
name: gitlab-secret
key: postgres.pwd.root
- name: SONARQUBE_JDBC_URL
value: "jdbc:postgresql://postgres:5432/sonar"
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: 300m
memory: 512Mi
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: sonarqube
namespace: jenkins
spec:
rules:
- host: sonar.luffy.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: sonarqube
port:
number: 9000
- sonarqube 服务端安装
# 创建sonar数据库
$ kubectl -n jenkins exec -ti postgres-5859dc6f58-mgqz9 bash
#/ psql
# create database sonar;
## 创建sonarqube服务器
$ kubectl create -f sonar.yaml
## 配置本地hosts解析
172.21.51.143 sonar.luffy.com
## 访问sonarqube初始用户名密码为 admin/admin
$ curl http://sonar.luffy.com
-
sonar-scanner的安装
下载地址 sonar
-
演示sonar代码扫描功能
- 在项目根目录中准备配置文件 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
-
配置 sonarqube 服务器地址
-
由于sonar-scanner 需要将扫描结果上报给 sonarqube 服务器做质量分析因此我们需要在 sonar-scanner 中配置 sonarqube 的服务器地址
-
在集群宿主机中测试先配置一下 hosts 文件然后配置 sonar 的地址
-
$ cat /etc/hosts
172.21.51.143 sonar.luffy.com
$ cat sonar-scanner/conf/sonar-scanner.properties
#----- Default SonarQube server
#sonar.host.url=http://localhost:9000
sonar.host.url=http://sonar.luffy.com
#----- Default source code encoding
#sonar.sourceEncoding=UTF-8
- 为了使所有的 pod 都可以通过
sonar.luffy.com
访问可以配置 coredns 的静态解析
# 静态解析
$ kubectl -n kube-system edit cm coredns
...
hosts {
172.21.51.143 jenkins.luffy.com gitlab.luffy.com sonar.luffy.com
fallthrough
}
- 执行扫描
## 在项目的根目录下执行
$ /opt/sonar-scanner-4.2.0.1873-linux/bin/sonar-scanner -X
-
sonarqube 界面查看结果
- 登录 sonarqube 界面查看结果Quality Gates 说明
java 项目的配置文件通常格式为
sonar.projectKey=eureka-cluster
sonar.projectName=eureka-cluster
# if you want disabled the DTD verification for a proxy problem for example, true by default
# JUnit like test report, default value is test.xml
sonar.sources=src/main/java
sonar.language=java
sonar.tests=src/test/java
sonar.java.binaries=target/classes
🍑 插件安装及配置
-
集成到 tools 容器中
由于我们的代码拉取、构建任务均是在 tools 容器中进行因此我们需要把 scanner 集成到我们的 tools 容器中又因为 scanner 是一个 cli 客户端因此我们直接把包解压好拷贝到 tools 容器内部配置一下 PATH 路径即可注意两点
-
直接在 tools 镜像中配置
http://sonar.luffy.com
-
由于 tools 已经集成了 java 环境因此可以直接剔除 scanner 自带的 jre
-
删掉
sonar-scanner/jre
目录 -
修改
sonar-scanner/bin/sonar-scanner
-
use_embedded_jre=false
$ cd tools
$ cp -r /opt/sonar-scanner-4.2.0.1873-linux/ sonar-scanner
## sonar配置由于我们是在Pod中使用也可以直接配置sonar.host.url=http://sonarqube:9000
$ cat sonar-scanner/conf/sonar-scanner.properties
#----- Default SonarQube server
sonar.host.url=http://sonar.luffy.com
#----- Default source code encoding
#sonar.sourceEncoding=UTF-8
$ rm -rf sonar-scanner/jre
$ vi sonar-scanner/bin/sonar-scanner
...
use_embedded_jre=false
...
Dockerfile
jenkins/custom-images/tools/Dockerfile2
FROM alpine:3.13.4
LABEL maintainer="inspur_lyx@hotmail.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 && \
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 172.21.51.143:5000/devops/tools:v2
$ docker push 172.21.51.143:5000/devops/tools:v2
-
修改 Jenkins PodTemplate
为了在新的构建任务中可以拉取 v2 版本的 tools 镜像需要更新 PodTemplate
-
安装并配置 sonar 插件
由于 sonarqube 的扫描的结果需要进行 Quality Gates 的检测那么我们在容器中执行完代码扫描任务后如何知道本次扫描是否通过了 Quality Gates那么就需要借助于 sonarqube 实现的 jenkins 的插件。
-
安装插件
插件中心搜索 sonarqube直接安装
-
配置插件
系统管理 -> 系统配置 -> SonarQube servers -> Add SonarQube
-
Namesonarqube
-
Server URL
http://sonar.luffy.com
-
Server authentication token
① 登录 sonarqube -> My Account -> Security -> Generate Token
② 登录 Jenkins添加全局凭据类型为 Secret text
-
-
如何在 jenkinsfile 中使用
我们在 官方介绍 中可以看到
-
🍑 Jenkinsfile集成sonarqube演示
jenkins/pipelines/p9.yaml
pipeline {
agent { label 'jnlp-slave'}
options {
buildDiscarder(logRotator(numToKeepStr: '10'))
disableConcurrentBuilds()
timeout(time: 20, unit: 'MINUTES')
gitLabConnection('gitlab')
}
environment {
IMAGE_REPO = "172.21.51.143:5000/myblog"
DINGTALK_CREDS = credentials('dingTalk')
TAB_STR = "\n \n "
}
stages {
stage('git-log') {
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 3
}
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 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' manifests/*"
timeout(time: 1, unit: 'MINUTES') {
sh "kubectl apply -f manifests/"
}
}
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**项目名称**luffy \n**Git log**: ${GIT_LOG} \n**构建分支**: ${BRANCH_NAME} \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**项目名称**luffy \n**Git log**: ${GIT_LOG} \n**构建分支**: ${BRANCH_NAME} \n**构建地址**${RUN_DISPLAY_URL} \n**构建任务**${BUILD_TASKS}"
}
}'
"""
}
always {
echo 'I will always say Hello again!'
}
}
}
若 Jenkins 执行任务过程中 sonarqube 端报类似下图的错
则需要在 sonarqube 服务端进行如下配置添加一个 webhook
3. Jenkins集成robotFramework
集成 RobotFramework 实现验收测试
一个基于 Python 语言用于验收测试和验收测试驱动开发ATDD的通用测试自动化框架提供了一套特定的语法并且有非常丰富的测试库 。
🍑 robot用例简介
robot/robot.txt
*** Settings ***
Library RequestsLibrary
Library SeleniumLibrary
*** Variables ***
${demo_url} http://myblog.luffy/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
ui
[Tags] critical
${chrome_options} = Evaluate sys.modules['selenium.webdriver'].ChromeOptions() sys, selenium.webdriver
Call Method ${chrome_options} add_argument headless
Call Method ${chrome_options} add_argument no-sandbox
${options}= Call Method ${chrome_options} to_capabilities
Open Browser ${demo_url}/ browser=chrome desired_capabilities=${options}
sleep 2s
Capture Page Screenshot
Page Should Contain Django
close browser
# 使用tools镜像启动容器来验证手动使用robotframework来做验收测试
$ docker run --rm -ti 172.21.51.143:5000/devops/tools:v2 bash
bash-5.0# apk add chromium chromium-chromedriver
$ cat 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
🍑 与 tools 工具镜像集成
FROM alpine:3.13.4
LABEL maintainer="inspur_lyx@hotmail.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/
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
# ------------------------------------------------#
$ docker build . -t 172.21.51.143:5000/devops/tools:v3
$ docker push 172.21.51.143:5000/devops/tools:v3
更新 Jenkins 中 kubernetes 中的 containers template
🍑 插件安装及配置
为什么要安装 robot 插件
-
安装 robotFramework
- 插件中心搜索 robotframework直接安装
- tools 集成 robot命令之前已经安装
-
与 jenkinsfile 的集成
container('tools') {
sh 'robot -i critical -d artifacts/ robot.txt'
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
}
🍑 实践通过Jenkinsfile实现demo项目的验收测试
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 = "172.21.51.143:5000/myblog"
DINGTALK_CREDS = credentials('dingTalk')
TAB_STR = "\n \n "
}
stages {
stage('git-log') {
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 3
}
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 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' manifests/*"
timeout(time: 1, unit: 'MINUTES') {
sh "kubectl apply -f manifests/;sleep 20;"
}
}
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**项目名称**luffy \n**Git log**: ${GIT_LOG} \n**构建分支**: ${BRANCH_NAME} \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**项目名称**luffy \n**Git log**: ${GIT_LOG} \n**构建分支**: ${BRANCH_NAME} \n**构建地址**${RUN_DISPLAY_URL} \n**构建任务**${BUILD_TASKS}"
}
}'
"""
}
always {
echo 'I will always say Hello again!'
}
}
}
在 Jenkins 中查看 robot 的构建结果。
4. 总结与反思
通过这三篇文章主要学习了以下几个方面
- 讲解最基础的 Jenkins 的使用
- Pipeline 流水线的使用
- Jenkinsfile 的使用
- 多分支流水线的使用
- 与 Kubernetes 集成动态 jnlp slave pod 的使用
- 与 sonarqube 集成实现代码扫描
- 与 Robotframework 集成实现验收测试
但是也存在一些问题
- Jenkinsfile 过于冗长
- 多个项目配置 Jenkinsfile存在很多重复内容
- 没有实现根据不同分支来部署到不同的环境
- Java 项目的构建
- k8s部署后采用等待的方式执行后续步骤不合理
后面会通过 sharedLibrary 对进行 CI/CD 流程进行一个优化