1、动态生成Slave
1.一、简介
以前咱们都是在物理机或者虚拟机上部署jenkins,可是这种部署方式会有一些难点,以下:node
- 主 Master 发生单点故障时,整个流程都不可用了
- 每一个 Slave 的配置环境不同,来完成不一样语言的编译打包等操做,可是这些差别化的配置致使管理起来很是不方便,维护起来也是比较费劲
- 资源分配不均衡,有的 Slave 要运行的 job 出现排队等待,而有的 Slave 处于空闲状态
- 资源有浪费,每台 Slave 多是物理机或者虚拟机,当 Slave 处于空闲状态时,也不会彻底释放掉资源。
正由于上面的这些种种痛点,咱们渴望一种更高效更可靠的方式来完成这个 CI/CD 流程,而 Docker 虚拟化容器技术能很好的解决这个痛点,又特别是在 Kubernetes 集群环境下面可以更好来解决上面的问题,下图是基于 Kubernetes 搭建 Jenkins 集群的简单示意图:
从图上能够看到 Jenkins Master 和 Jenkins Slave 以 Pod 形式运行在 Kubernetes 集群的 Node 上,Master 运行在其中一个节点,而且将其配置数据存储到一个 Volume 上去,Slave 运行在各个节点上,而且它不是一直处于运行状态,它会按照需求动态的建立并自动删除。
这种方式的工做流程大体为:当 Jenkins Master 接受到 Build 请求时,会根据配置的 Label 动态建立一个运行在 Pod 中的 Jenkins Slave 并注册到 Master 上,当运行完 Job 后,这个 Slave 会被注销而且这个 Pod 也会自动删除,恢复到最初状态。
这种方式部署给咱们带来以下好处:
python
- 服务高可用,当 Jenkins Master 出现故障时,Kubernetes 会自动建立一个新的 Jenkins Master 容器,而且将 Volume 分配给新建立的容器,保证数据不丢失,从而达到集群服务高可用。
- 动态伸缩,合理使用资源,每次运行 Job 时,会自动建立一个 Jenkins Slave,Job 完成后,Slave 自动注销并删除容器,资源自动释放,并且 Kubernetes 会根据每一个资源的使用状况,动态分配 Slave 到空闲的节点上建立,下降出现因某节点资源利用率高,还排队等待在该节点的状况。
- 扩展性好,当 Kubernetes 集群的资源严重不足而致使 Job 排队等待时,能够很容易的添加一个 Kubernetes Node 到集群中,从而实现扩展。
1.二、部署
一、建立PV、PVC,为Jenkins提供数据持久化:git
--- apiVersion: v1 kind: PersistentVolume metadata: name: jenkins-pv spec: capacity: storage: 5Gi accessModes: - ReadWriteMany persistentVolumeReclaimPolicy: Delete nfs: server: 172.16.1.128 path: /data/k8s/jenkins --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: jenkins-pvc namespace: devops spec: accessModes: - ReadWriteMany resources: requests: storage: 5Gi
二、建立角色受权
github
apiVersion: v1 kind: ServiceAccount metadata: name: jenkins-sa namespace: devops --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRole metadata: name: jenkins-cr rules: - apiGroups: ["extensions", "apps"] resources: ["deployments"] verbs: ["create", "delete", "get", "list", "watch", "patch", "update"] - apiGroups: [""] resources: ["services"] verbs: ["create", "delete", "get", "list", "watch", "patch", "update"] - apiGroups: [""] resources: ["pods"] verbs: ["create","delete","get","list","patch","update","watch"] - apiGroups: [""] resources: ["pods/exec"] verbs: ["create","delete","get","list","patch","update","watch"] - apiGroups: [""] resources: ["pods/log"] verbs: ["get","list","watch"] - apiGroups: [""] resources: ["secrets"] verbs: ["get"] --- apiVersion: rbac.authorization.k8s.io/v1beta1 kind: ClusterRoleBinding metadata: name: jenkins-crd roleRef: kind: ClusterRole name: jenkins-cr apiGroup: rbac.authorization.k8s.io subjects: - kind: ServiceAccount name: jenkins-sa namespace: devops
一、在Kubernetes中部署Jenkins,新建Deployment,jenkins-deploy.yaml
web
--- apiVersion: extensions/v1beta1 kind: Deployment metadata: name: jenkins namespace: devops spec: template: metadata: labels: app: jenkins spec: terminationGracePeriodSeconds: 10 serviceAccount: jenkins-sa containers: - name: jenkins image: jenkins/jenkins:lts imagePullPolicy: IfNotPresent env: - name:JAVA_OPTS value: -XshowSettings:vm -Dhudson.slaves.NodeProvisioner.initialDelay=0 -Dhudson.slaves.NodeProvisioner.MARGIN=50 -Dhudson.slaves.NodeProvisioner.MARGIN0=0.85 -Duser.timezone=Asia/Shanghai ports: - containerPort: 8080 name: web protocol: TCP - containerPort: 50000 name: agent protocol: TCP resources: limits: cpu: 1000m memory: 1Gi requests: cpu: 500m memory: 512Mi livenessProbe: httpGet: path: /login port: 8080 initialDelaySeconds: 60 timeoutSeconds: 5 failureThreshold: 12 readinessProbe: httpGet: path: /login port: 8080 initialDelaySeconds: 60 timeoutSeconds: 5 failureThreshold: 12 volumeMounts: - name: jenkinshome mountPath: /var/jenkins_home securityContext: fsGroup: 1000 volumes: - name: jenkinshome persistentVolumeClaim: claimName: jenkins-pvc --- apiVersion: v1 kind: Service metadata: name: jenkins namespace: devops labels: app: jenkins spec: selector: app: jenkins type: NodePort ports: - name: web port: 8080 targetPort: web nodePort: 30002 - name: agent port: 50000 targetPort: agent
五、建立上面的资源清单
docker
# kubectl apply -f jenkins-rbac.yaml # kubectl apply -f jenkins-pvc.yaml # kubectl apply -f jenkins-deploy.yaml
启动若是报以下错误(由于咱们容器里是以jenkins用户启动,而咱们NFS服务器上是root启动,因此没有权限):
shell
[root@master manifests]# kubectl logs jenkins-688c6cd5fd-lj6zg -n devops touch: cannot touch '/var/jenkins_home/copy_reference_file.log': Permission denied Can not write to /var/jenkins_home/copy_reference_file.log. Wrong volume permissions?
而后给咱们NFS服务器上的目录受权便可:
api
# chown -R 1000 /data/k8s/jenkins/jenkins
而后登陆网站,由于咱们Service是采用NodePort类型,其端口为30002,咱们直接在浏览器用这个端口访问:
密码能够经过以下命令得到:
浏览器
# cat /data/k8s/jenkins/secrets/initialAdminPassword 12b503a274354e09a465b4f76664db70
而后安装插件到安装完成。
安全
1.三、配置
一、安装插件kubernetes
二、填写Kubernetes和Jenkins的配置信息
配置管理->系统配置->新增cloud。
按照图中红色框中填写,其中Kubernetes命名空间填写咱们Jenkins所在的命名空间。
备注:
若是链接测试失败,极可能是权限问题,咱们就须要把ServiceAccount的凭证jenkins-sa添加进来。
三、配置Pod模板
另外须要挂载两个主机目录:
- /var/run/docker.sock:该文件是用于 Pod 中的容器可以共享宿主机的 Docker;
- /root/.kube:这个目录挂载到容器的/root/.kube目录下面这是为了让咱们可以在 Pod 的容器中可以使用 kubectl 工具来访问咱们的 Kubernetes 集群,方便咱们后面在 Slave Pod 部署 Kubernetes 应用;
避免一些权限不足,须要配置ServiceAccount
1.四、测试
一、建立一个项目
二、在标签位置填写咱们前面模板中定义的Label
三、直接在构建处执行shell进行测试
而后点击构建,在终端能够看到整个过程:
[root@master manifests]# kubectl get pod -n devops -w NAME READY STATUS RESTARTS AGE jenkins-6595ddd5d-m5fvd 1/1 Running 0 144m jenkins-slave-kkc2b 0/1 Pending 0 0s jenkins-slave-kkc2b 0/1 Pending 0 0s jenkins-slave-kkc2b 0/1 ContainerCreating 0 0s jenkins-slave-kkc2b 1/1 Running 0 3s jenkins-slave-kkc2b 1/1 Terminating 0 31s jenkins-slave-kkc2b 1/1 Terminating 0 31s
也能够在jenkins里看日志以下:
2、Pipeline
2.一、简介
Pipeline,简单来讲,就是一套运行在 Jenkins 上的工做流框架,将原来独立运行于单个或者多个节点的任务链接起来,实现单个任务难以完成的复杂流程编排和可视化的工做。
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 命令同样。
Pipeline的使用:
- Pipeline 脚本是由 Groovy 语言实现的
- Pipeline 支持两种语法:Declarative(声明式)和 Scripted Pipeline(脚本式)语法
- Pipeline 也有两种建立方法:能够直接在 Jenkins 的 Web UI 界面中输入脚本;也能够经过建立一个 Jenkinsfile 脚本文件放入项目源码库中
- 通常咱们都推荐在 Jenkins 中直接从源代码控制(SCMD)中直接载入 Jenkinsfile Pipeline 这种方法
2.二、建立
2.2.一、简单的Pipeline
直接 在Jenkins的WEB UI上输入脚本。
脚本内容:
node { stage('Clone') { echo "1.Clone Stage" } stage('Test') { echo "2.Test Stage" } stage('Build') { echo "3.Build Stage" } stage('Deploy') { echo "4. Deploy Stage" } }
而后保存--> 点击构建--> 观察日志
输出符合咱们脚本内容。
2.2.二、在slave中运行Pipeline
上面对Jenkins的Pipeline作了简单的测试,可是其并未在咱们的Slave中运行,若是要在Slave中运行,其就要使用咱们前面添加的Label,以下:
node('joker-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" } }
而后咱们保存并点击构建,观察Pod的变化:
[root@master ~]# kubectl get pod -n devops -w NAME READY STATUS RESTARTS AGE jenkins-6595ddd5d-m5fvd 1/1 Running 0 2d23h jenkins-slave-vq8wf 0/1 Pending 0 0s jenkins-slave-vq8wf 0/1 Pending 0 0s jenkins-slave-vq8wf 0/1 ContainerCreating 0 0s jenkins-slave-vq8wf 1/1 Running 0 2s jenkins-slave-vq8wf 1/1 Terminating 0 27s jenkins-slave-vq8wf 1/1 Terminating 0 27s
咱们能够看到其依据咱们定义的模板动态生成了jenkins-slave的Pod,咱们在Jenkins的日志中查看:
能够看到两个的名字是同样的。
2.2.三、部署完整应用
部署应用的流程以下:
- 编写代码
- 测试
- 编写 Dockerfile
- 构建打包 Docker 镜像
- 推送 Docker 镜像到仓库
- 编写 Kubernetes YAML 文件
- 更改 YAML 文件中 Docker 镜像 TAG
- 利用 kubectl 工具部署应用
因此基本的Pipeline脚本框架应该以下:
node('joker-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" } }
第一步:克隆代码
stage('Clone') { echo "1.Clone Stage" git url: "https://github.com/baidjay/jenkins-demo.git" script { build_tag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim() } echo "${build_tag}" }
咱们这里采用和git commit的记录为镜像的 tag,这里有一个好处就是镜像的 tag 能够和 git 提交记录对应起来,也方便往后对应查看。可是因为这个 tag 不仅是咱们这一个 stage 须要使用,下一个推送镜像是否是也须要,因此这里咱们把这个 tag 编写成一个公共的参数,把它放在 Clone 这个 stage 中。
第二步:测试
stage('Test') { echo "2.Test Stage" }
测试能够是单测,也能够是工具,咱们这里就简单存在这个步骤。
第三步:构建镜像
stage('Build') { echo "3.Build Docker Image Stage" sh "docker build -t registry.cn-hangzhou.aliyuncs.com/ik9s/jenkins-demo:${build_tag} ." }
这一步咱们就使用到上面定义的build_tag变量。
第四步:推送镜像
stage('Push') { echo "4.Push Docker Image Stage" sh "docker login --username=www.565361785@qq.com registry.cn-hangzhou.aliyuncs.com -p xxxxx" sh "docker push registry.cn-hangzhou.aliyuncs.com/ik9s/jenkins-demo:${build_tag}" }
配置Jenkins,隐藏用户名密码信息:
其中ID:AliRegistry 是咱们后面要用的值。
这样咱们上面的脚本就能够定义以下:
stage('Push') { echo "4.Push Docker Image Stage" withCredentials([usernamePassword(credentialsId: 'AliRegistry', passwordVariable: 'AliRegistryPassword', usernameVariable: 'AliRegistryUser')]) { sh "docker login -u ${AliRegistryUser} registry.cn-hangzhou.aliyuncs.com -p ${AliRegistryPassword}" sh "docker push registry.cn-hangzhou.aliyuncs.com/ik9s/jenkins-demo:${build_tag}" } }
注意咱们这里在 stage 中使用了一个新的函数withCredentials,其中有一个 credentialsId 值就是咱们刚刚建立的 ID 值,而对应的用户名变量就是 ID 值加上 User,密码变量就是 ID 值加上 Password,而后咱们就能够在脚本中直接使用这里两个变量值来直接替换掉以前的登陆 docker hub 的用户名和密码,如今是否是就很安全了,我只是传递进去了两个变量而已,别人并不知道个人真正用户名和密码,只有咱们本身的 Jenkins 平台上添加的才知道。
第五步:更改YAML文件
stage('YAML') { echo "5. Change YAML File Stage" sh "sed -i 's/<BUILD_TAG>/${build_tag}/' k8s.yaml" sh "sed -i 's/<BRANCH_NAME>/${env.BRANCH_NAME}/' k8s.yaml" }
其YAML文件为(YAML文件放在项目根目录):
apiVersion: extensions/v1beta1 kind: Deployment metadata: name: jenkins-demo spec: template: metadata: labels: app: jenkins-demo spec: containers: - image: registry.cn-hangzhou.aliyuncs.com/ik9s/jenkins-demo:<BUILD_TAG> imagePullPolicy: IfNotPresent name: jenkins-demo env: - name: branch value: <BRANCH_NAME>
第六步:部署
部署阶段咱们增长人工干预,可能须要将该版本先发布到测试环境、QA 环境、或者预览环境之类的,总之直接就发布到线上环境去仍是挺少见的,因此咱们须要增长人工确认的环节,通常都是在 CD 的环节才须要人工干预,好比咱们这里的最后两步,咱们就能够在前面加上确认,好比:
咱们将YAML这一步改成:
stage('YAML') { echo "5. Change YAML File Stage" def userInput = input( id: 'userInput', message: 'Choose a deploy environment', parameters: [ [ $class: 'ChoiceParameterDefinition', choices: "Dev\nQA\nProd", name: 'Env' ] ] ) echo "This is a deploy step to ${userInput.Env}" sh "sed -i 's/<BUILD_TAG>/${build_tag}/' k8s.yaml" sh "sed -i 's/<BRANCH_NAME>/${env.BRANCH_NAME}/' k8s.yaml" sh "sed -i 's#cnych/jenkins-demo#registry.cn-hangzhou.aliyuncs.com/ik9s/jenkins-demo#' k8s.yaml" }
而后再部署阶段:
stage('Deploy') { echo "6. Deploy Stage" if (userInput.Env == "Dev") { // deploy dev stuff } else if (userInput.Env == "QA"){ // deploy qa stuff } else { // deploy prod stuff } sh "kubectl apply -f k8s.yaml" }
因为这一步也属于部署的范畴,因此咱们能够将最后两步都合并成一步,咱们最终的Pipeline脚本以下:
node('joker-jnlp') { stage('Clone') { echo "1.Clone Stage" git url: "https://github.com/baidjay/jenkins-demo.git" script { build_tag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim() } echo "${build_tag}" } stage('Test') { echo "2.Test Stage" } stage('Build') { echo "3.Build Docker Image Stage" sh "docker build -t registry.cn-hangzhou.aliyuncs.com/ik9s/jenkins-demo:${build_tag} ." } stage('Push') { echo "4.Push Docker Image Stage" withCredentials([usernamePassword(credentialsId: 'AliRegistry', passwordVariable: 'AliRegistryPassword', usernameVariable: 'AliRegistryUser')]) { sh "docker login -u ${AliRegistryUser} registry.cn-hangzhou.aliyuncs.com -p ${AliRegistryPassword}" sh "docker push registry.cn-hangzhou.aliyuncs.com/ik9s/jenkins-demo:${build_tag}" } } stage('Deploy') { echo "5. Change YAML File Stage" def userInput = input( id: 'userInput', message: 'Choose a deploy environment', parameters: [ [ $class: 'ChoiceParameterDefinition', choices: "Dev\nQA\nProd", name: 'Env' ] ] ) echo "This is a deploy step to ${userInput}" sh "sed -i 's/<BUILD_TAG>/${build_tag}/' k8s.yaml" sh "sed -i 's/<BRANCH_NAME>/${env.BRANCH_NAME}/' k8s.yaml" echo "6. Deploy Stage" if (userInput == "Dev") { // deploy dev stuff } else if (userInput == "QA"){ // deploy qa stuff } else { // deploy prod stuff } sh "kubectl apply -f k8s.yaml -n default" } }
而后构建面板以下:
而后查看Pod日志以下:
[root@master jenkins]# kubectl logs jenkins-demo-789fdc6878-5pzbx Hello, Kubernetes!I'm from Jenkins CI!
2.2.四、Jenkinsfile
万里长征,貌似咱们的任务完成了,其实否则,咱们这里只是完成了一次手动的添加任务的构建过程,在实际的工做实践中,咱们更多的是将 Pipeline 脚本写入到 Jenkinsfile 文件中,而后和代码一块儿提交到代码仓库中进行版本管理。如今咱们将上面的 Pipeline 脚本拷贝到一个 Jenkinsfile 中,将该文件放入上面的 git 仓库中,可是要注意的是,如今既然咱们已经在 git 仓库中了,是否是就不须要 git clone 这一步骤了,因此咱们须要将第一步 Clone 操做中的 git clone 这一步去掉。
以下:
node('joker-jnlp') { stage('Prepare') { echo "1.Prepare Stage" script { build_tag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim() } echo "${build_tag}" } stage('Test') { echo "2.Test Stage" } stage('Build') { echo "3.Build Docker Image Stage" sh "docker build -t registry.cn-hangzhou.aliyuncs.com/ik9s/jenkins-demo:${build_tag} ." } stage('Push') { echo "4.Push Docker Image Stage" withCredentials([usernamePassword(credentialsId: 'AliRegistry', passwordVariable: 'AliRegistryPassword', usernameVariable: 'AliRegistryUser')]) { sh "docker login -u ${AliRegistryUser} registry.cn-hangzhou.aliyuncs.com -p ${AliRegistryPassword}" sh "docker push registry.cn-hangzhou.aliyuncs.com/ik9s/jenkins-demo:${build_tag}" } } stage('Deploy') { echo "5. Change YAML File Stage" def userInput = input( id: 'userInput', message: 'Choose a deploy environment', parameters: [ [ $class: 'ChoiceParameterDefinition', choices: "Dev\nQA\nProd", name: 'Env' ] ] ) echo "This is a deploy step to ${userInput}" sh "sed -i 's/<BUILD_TAG>/${build_tag}/' k8s.yaml" sh "sed -i 's/<BRANCH_NAME>/${env.BRANCH_NAME}/' k8s.yaml" echo "6. Deploy Stage" if (userInput == "Dev") { // deploy dev stuff } else if (userInput == "QA"){ // deploy qa stuff } else { // deploy prod stuff } sh "kubectl apply -f k8s.yaml -n default" } }
而后咱们更改上面的 jenkins-demo 这个任务,点击 Configure -> 最下方的 Pipeline 区域 -> 将以前的 Pipeline Script 更改为 Pipeline Script from SCM,而后根据咱们的实际状况填写上对应的仓库配置,要注意 Jenkinsfile 脚本路径。
在实际的项目中,每每一个代码仓库都会有不少分支的,好比开发、测试、线上这些分支都是分开的,通常状况下开发或者测试的分支咱们但愿提交代码后就直接进行 CI/CD 操做,而线上的话最好增长一我的工干预的步骤,这就须要 Jenkins 对代码仓库有多分支的支持,固然这个特性是被 Jenkins 支持的。
而后新建一个 Jenkinsfile 文件,配置以下:
node('joker-jnlp') { stage('Prepare') { echo "1.Prepare Stage" checkout scm script { build_tag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim() if (env.BRANCH_NAME != 'master') { build_tag = "${env.BRANCH_NAME}-${build_tag}" } } } stage('Test') { echo "2.Test Stage" } stage('Build') { echo "3.Build Docker Image Stage" sh "docker build -t registry.cn-hangzhou.aliyuncs.com/ik9s/jenkins-demo:${build_tag} ." } stage('Push') { echo "4.Push Docker Image Stage" withCredentials([usernamePassword(credentialsId: 'AliRegistry', passwordVariable: 'AliRegistryPassword', usernameVariable: 'AliRegistryUser')]) { sh "docker login -u ${AliRegistryUser} registry.cn-hangzhou.aliyuncs.com -p ${AliRegistryPassword}" sh "docker push registry.cn-hangzhou.aliyuncs.com/ik9s/jenkins-demo:${build_tag}" } } stage('Deploy') { echo "5. Deploy Stage" if (env.BRANCH_NAME == 'master') { input "确认要部署线上环境吗?" } sh "sed -i 's/<BUILD_TAG>/${build_tag}/' k8s.yaml" sh "sed -i 's/<BRANCH_NAME>/${env.BRANCH_NAME}/' k8s.yaml" sh "kubectl apply -f k8s.yaml --record" } }
在第一步中咱们增长了checkout scm命令,用来检出代码仓库中当前分支的代码,为了不各个环境的镜像 tag 产生冲突,咱们为非 master 分支的代码构建的镜像增长了一个分支的前缀,在第五步中若是是 master 分支的话咱们才增长一个确认部署的流程,其余分支都自动部署,而且还须要替换 k8s.yaml 文件中的环境变量的值。
3、BlueOcean
咱们这里使用 BlueOcean 这种方式来完成此处 CI/CD 的工做,BlueOcean 是 Jenkins 团队从用户体验角度出发,专为 Jenkins Pipeline 从新设计的一套 UI 界面,仍然兼容之前的 fressstyle 类型的 job,BlueOcean 具备如下的一些特性:
- 连续交付(CD)Pipeline 的复杂可视化,容许快速直观的了解 Pipeline 的状态
- 能够经过 Pipeline 编辑器直观的建立 Pipeline
- 须要干预或者出现问题时快速定位,BlueOcean 显示了 Pipeline 须要注意的地方,便于异常处理和提升生产力
- 用于分支和拉取请求的本地集成能够在 GitHub 或者 Bitbucket 中与其余人进行代码协做时最大限度提升开发人员的生产力。
BlueOcean 能够安装在现有的 Jenkins 环境中,也可使用 Docker 镜像的方式直接运行,咱们这里直接在现有的 Jenkins 环境中安装 BlueOcean 插件:登陆 Jenkins Web UI -> 点击左侧的 Manage Jenkins -> Manage Plugins -> Available -> 搜索查找 BlueOcean -> 点击下载安装并重启
3.一、建立Pipeline
点击建立:
获取Token的步骤:
而后获取Token:
建立完成以下所示: