Jenkins Pipeline 构建
· 阅读需 6 分钟
Jenkins pipeline 构建
Jenkins Pipeline 有几个核心概念:
- Node:节点,一个 Node 就是一个 Jenkins 节点,Master 或者 Agent,是执行 Step 的具体运行环境,比如我们之前动态运行的 Jenkins Slave 就是一个 Node 节点
- Stage:阶段,一个 Pipeline 可以划分为若干个 Stage,每个 Stage 代表一组操作,比如:Build、Test、Deploy,Stage 是一个逻辑分组的概念,可以跨多个 Node
- Step:步骤,Step 是最基本的操作单元,可以是打印一句话,也可以是构建一个 Docker 镜像,由各类 Jenkins 插件提供,比如命令:sh 'make',就相当于我们平时 shell 终端中执行 make 命令一样。
如何创建 Jenkins Pipline 呢?
- Pipeline 脚本是由 Groovy 语言实现的,但是我们没必要单独去学习 Groovy,当然你会的话最好
- Pipeline 支持两种语法:Declarative(声明式)和 Scripted Pipeline(脚本式)语法
- Pipeline 也有两种创建方法:可以直接在 Jenkins 的 Web UI 界面中输入脚本;也 可以通过创建一个 Jenkinsfile 脚本文件放入项目源码库中
- 一般我们都推荐在 Jenkins 中直接从源代码控制(SCMD)中直接载入 Jenkinsfile Pipeline 这种方法
安装插件
-
Pipeline
-
Kubernetes (能够动态的生成 Slave 的 Pod)
-
Pipeline: Stage View Plugin (可视化构建)
Slave 构建任务
实例:
# 添加 Slave Pod 时,添加的 label 给 node 添加一个 lable 属性
node('salve-jnlp') {
stage('Clone') {
echo "1.Clone Stage"
}
stage('Test') {
echo "2.Test Stage"
}
stage('Build') {
echo "3.Build Stage"
}
stage('Deploy') {
echo "4. Deploy Stage"
}
}
构建前:
$ kubectl get pods -n kube-ops
NAME READY STATUS RESTARTS AGE
jenkins-bb4b795c5-wkpkq 1/1 Running 3 (6h59m ago) 3d23h
......
构建后:
$ kubectl get pods -n kube-ops
NAME READY STATUS RESTARTS AGE
jenkins-agent-8x175 1/1 ContainerCreating 0 0s
jenkins-bb4b795c5-wkpkq 1/1 Running 3 (6h59m ago) 3d23h
......
构建完成后:
这个 agent pod 就不在了
部署 kubernetes 应用
node('slave-jnlp') {
stage('Clone') {
echo "1.Clone Stage"
}
stage('Test') {
echo "2.Test Stage"
}
stage('Build') {
echo "3.Build Docker Image Stage"
}
stage('Push') {
echo "4.Push Docker Image Stage"
}
stage('YAML') {
echo "5.Change YAML File Stage"
}
stage('Deploy') {
echo "6.Deploy Stage"
}
}
使用 podTemplate
定义个阶段容器
Clone 代码 -> 单元测试 -> Golang 编译打包 -> Docker 镜像构建/推送 -> Kubectl 部署服务。
使用 kubectl 需提前将目录
/root/.kube
挂载到容器的/root/.kube
目录下面,才可以使用 kubectl另外如果在配置了后运行 Slave Pod 的时候出现了权限问题,这是因为 Jenkins Slave Pod 中没有配置权限,所以需要配置上 ServiceAccount,在 Slave Pod 配置的地方点击下面的高级,添加上对应的 ServiceAccount 即可
这里写了一段很简单的定义 podTemplate
def label = "slave-${UUID.randomUUID().toString()}"
podTemplate(label: label, containers: [
containerTemplate(name: 'golang', image: 'golang:1.18.3-alpine3.16', command: 'cat', ttyEnabled: true),
containerTemplate(name: 'docker', image: 'docker:latest', command: 'cat', ttyEnabled: true),
containerTemplate(name: 'kubectl', image: 'cnych/kubectl', command: 'cat', ttyEnabled: true)
], serviceAccount: 'jenkins', volumes: [
hostPathVolume(mountPath: '/home/jenkins/.kube', hostPath: '/root/.kube')
], envVars: [
envVar(key: 'DOCKER_HOST', value: 'tcp://docker-dind:2375') // 环境变量
]) {
node(label) {
def myRepo = checkout scm
def gitCommit = myRepo.GIT_COMMIT
def gitBranch = myRepo.GIT_BRANCH
stage('单元测试') {
echo "测试阶段"
}
stage('代码编译打包') {
container('golang') {
echo "代码编译打包阶段"
}
}
stage('构建 Docker 镜像') {
container('docker') {
echo "构建 Docker 镜像阶段"
}
}
stage('运行 Kubectl') {
container('kubectl') {
echo "查看 K8S 集群 Pod 列表"
sh "kubectl get pods"
}
}
}
}
直接贴一个完整的 Jenkinsfile ,有需要直接改动吧
def label = "slave-${UUID.randomUUID().toString()}"
def helmLint(String chartDir) {
println "校验 chart 模板"
sh "helm lint ${chartDir}"
}
def helmDeploy(Map args) {
if (args.debug) {
println "Debug 应用"
sh "helm upgrade --dry-run --debug --install ${args.name} ${args.chartDir} -f ${args.valuePath} --set image.tag=${args.imageTag} --namespace ${args.namespace}"
} else {
println "部署应用"
sh "helm upgrade --install ${args.name} ${args.chartDir} -f ${args.valuePath} --set image.tag=${args.imageTag} --namespace ${args.namespace}"
echo "应用 ${args.name} 部署成功. 可以使用 helm status ${args.name} 查看应用状态"
}
}
podTemplate(label: label, containers: [
containerTemplate(name: 'golang', image: 'golang:1.14.2-alpine3.11', command: 'cat', ttyEnabled: true),
containerTemplate(name: 'docker', image: 'docker:latest', command: 'cat', ttyEnabled: true),
containerTemplate(name: 'helm', image: 'cnych/helm', command: 'cat', ttyEnabled: true),
containerTemplate(name: 'kubectl', image: 'cnych/kubectl', command: 'cat', ttyEnabled: true)
], serviceAccount: 'jenkins', envVars: [
envVar(key: 'DOCKER_HOST', value: 'tcp://docker-dind:2375') // 环境变量
]) {
node(label) {
def myRepo = checkout scm
// 获取 git commit id 作为镜像标签
def imageTag = sh(script: "git rev-parse --short HEAD", returnStdout: true).trim()
// 仓库地址
def registryUrl = "harbor.k8s.local"
def imageEndpoint = "course/devops-demo"
// 镜像
def image = "${registryUrl}/${imageEndpoint}:${imageTag}"
stage('单元测试') {
echo "测试阶段"
}
stage('代码编译打包') {
try {
container('golang') {
echo "2.代码编译打包阶段"
sh """
export GOPROXY=https://goproxy.cn
GOOS=linux GOARCH=amd64 go build -v -o demo-app
"""
}
} catch (exc) {
println "构建失败 - ${currentBuild.fullDisplayName}"
throw(exc)
}
}
stage('构建 Docker 镜像') {
withCredentials([[$class: 'UsernamePasswordMultiBinding',
credentialsId: 'docker-auth',
usernameVariable: 'DOCKER_USER',
passwordVariable: 'DOCKER_PASSWORD']]) {
container('docker') {
echo "3. 构建 Docker 镜像阶段"
sh """
cat /etc/resolv.conf
docker login ${registryUrl} -u ${DOCKER_USER} -p ${DOCKER_PASSWORD}
docker build -t ${image} .
docker push ${image}
"""
}
}
}
stage('运行 Helm') {
withCredentials([file(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')]) {
container('helm') {
sh "mkdir -p ~/.kube && cp ${KUBECONFIG} ~/.kube/config"
echo "4.开始 Helm 部署"
def userInput = input(
id: 'userInput',
message: '选择一个部署环境',
parameters: [
[
$class: 'ChoiceParameterDefinition',
choices: "Dev\nQA\nProd",
name: 'Env'
]
]
)
echo "部署应用到 ${userInput} 环境"
// 选择不同环境下面的 values 文件
if (userInput == "Dev") {
// deploy dev stuff
} else if (userInput == "QA"){
// deploy qa stuff
} else {
// deploy prod stuff
}
helmDeploy(
debug : false,
name : "devops-demo",
chartDir : "./helm",
namespace : "kube-ops",
valuePath : "./helm/my-values.yaml",
imageTag : "${imageTag}"
)
}
}
}
stage('运行 Kubectl') {
withCredentials([file(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')]) {
container('kubectl') {
sh "mkdir -p ~/.kube && cp ${KUBECONFIG} ~/.kube/config"
echo "5.查看应用"
sh "kubectl get all -n kube-ops -l app=devops-demo"
}
}
}
stage('快速回滚\?') {
withCredentials([file(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')]) {
container('helm') {
sh "mkdir -p ~/.kube && cp ${KUBECONFIG} ~/.kube/config"
def userInput = input(
id: 'userInput',
message: '是否需要快速回滚?',
parameters: [
[
$class: 'ChoiceParameterDefinition',
choices: "Y\nN",
name: '回滚\?'
]
]
)
if (userInput == "Y") {
sh "helm rollback devops-demo --namespace kube-ops"
}
}
}
}
}
}