kubernetes的DevOps业务(二):Jenkins,GitLab,Harbor,Tekton,GitOps

Jenkins流水线

Jenkins Pipeline

要实现在 Jenkins 中的构建(构建任务)工作,可以有多种方式,我们这里采用比较常用的 Pipeline 这种方式。Pipeline,简单来说,就是一套运行在 Jenkins 上的工作流框架,将原来独立运行于单个或者多个节点的任务连接起来,实现单个任务难以完成的复杂流程编排和可视化的工作。

Jenkins Pipeline 有几个核心概念:
1.Node:节点,一个 Node 就是一个 Jenkins 节点,Master 或者 Agent,是执行 Step 的具体运行环境,比如我们之前动态运行的 Jenkins Slave 就是一个 Node 节点
2.Stage:阶段,一个 Pipeline 可以划分为若干个 Stage,每个 Stage 代表一组操作,比如:Build、Test、Deploy,Stage 是一个逻辑分组的概念,可以跨多个 Node
3.Step:步骤,Step 是最基本的操作单元,可以是打印一句话,也可以是构建一个 Docker 镜像,由各类 Jenkins 插件提供,比如命令:sh ‘make’,就相当于我们平时 shell 终端中执行 make 命令一样。

那么我们如何创建 Jenkins Pipline 呢?
1.Pipeline 脚本是由 Groovy 语言实现的,但是我们没必要单独去学习 Groovy,当然你会的话最好
2.Pipeline 支持两种语法:Declarative(声明式)和 Scripted Pipeline(脚本式)语法
3.Pipeline 也有两种创建方法:可以直接在 Jenkins 的 Web UI 界面中输入脚本;也可以通过创建一个 Jenkinsfile 脚本文件放入项目源码库中
4.一般我们都推荐在 Jenkins 中直接从源代码控制(SCMD)中直接载入 Jenkinsfile Pipeline 这种方法

我们这里来给大家快速创建一个简单的 Pipeline,直接在 Jenkins 的 Web UI 界面中输入脚本运行
1.新建任务:在 Web UI 中点击 新建任务 -> 输入名称:pipeline-demo -> 选择下面的 流水线 -> 点击 确定
在这里插入图片描述

2.配置:在最下方的 Pipeline 区域输入如下 Script 脚本,然后点击保存。

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"
  }
}

在这里插入图片描述
3.构建:点击左侧区域的 立即构建,可以看到 Job 开始构建了
在这里插入图片描述
在这里插入图片描述
console output 我们可以看到上面我们 Pipeline 脚本中的4条输出语句都打印出来了,证明是符合我们的预期的。

如果大家对 Pipeline 语法不是特别熟悉的,可以前往输入脚本的下面的链接 流水线语法 中进行查看,这里有很多关于 Pipeline 语法的介绍,也可以自动帮我们生成一些脚本。
在这里插入图片描述
(看不到阶段视图,去安装stage view插件)

在 Slave 中构建任务

上面我们创建了一个简单的 Pipeline 任务,但是我们可以看到这个任务并没有在 Jenkins 的 Slave 中运行,那么如何让我们的任务跑在 Slave 中呢?还记得上节课我们在添加 Slave Pod 的时候,一定要记住添加的 label 吗?没错,我们就需要用到这个 label,我们重新编辑上面创建的 Pipeline 脚本,给 node 添加一个 label 属性(这个是前面定义pod的标签,就是在哪个节点上用pod来跑job),如下:

node('ydzs-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"
  }
}

(这里可以直接跳过pipeline脚本agent{})
我们这里只是给 node 添加了一个 ydzs-jnlp 这样的一个 label,然后我们保存,构建之前查看下 kubernetes 集群中的 Pod:

kubectl get pod -n kube-ops	

我们发现多了一个名叫 jenkins-agent-6gw0w 的 Pod 正在运行(名称一般是jenkins-agent-xxxxx),隔一会儿这个 Pod 就不再了。这也证明我们的 Job 构建完成了,同样回到 Jenkins 的 Web UI 界面中查看 Console Output,可以看到如下的信息:
在这里插入图片描述
证明我们当前的任务在跑在上面动态生成的这个 Pod 中,也符合我们的预期。我们回到 Job 的主界面,也可以看到大家可能比较熟悉的 阶段视图 界面:
想看阶段视图得安装这个:
在这里插入图片描述

部署 Kubernetes 应用

上面我们已经知道了如何在 Jenkins Slave 中构建任务了,那么如何来部署一个原生的 Kubernetes 应用呢? 要部署 Kubernetes 应用,我们就得对我们之前部署应用的流程要非常熟悉才行,我们之前的流程是怎样的:

编写代码
测试
编写 Dockerfile
构建打包 Docker 镜像
推送 Docker 镜像到仓库
编写 Kubernetes YAML 文件
更改 YAML 文件中 Docker 镜像 TAG
利用 kubectl 工具部署应用

我们之前在 Kubernetes 环境中部署一个原生应用的流程应该基本上是上面这些流程吧?现在我们就需要把上面这些流程放入 Jenkins 中来自动帮我们完成(当然编码除外),从测试到更新 YAML 文件属于 CI 流程后面部署属于 CD 的流程。如果按照我们上面的示例,我们现在要来编写一个 Pipeline 的脚本,应该怎么编写呢?

node('ydzs-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"
    }
}

现在我们创建一个流水线的作业,直接使用上面的脚本来构建,同样可以得到正确的结果:
在这里插入图片描述
克隆项目代码->编写代码,功能实现->编写dockerfile->构建镜像(将代码功能或者说应用做成镜像,以容器方式提供服务)->提交镜像到仓库->设置yaml中的镜像或者修改镜像的标签->以pod方式部署

这里我们来将一个简单 golang 程序,部署到 kubernetes 环境中,代码链接:https://github.com/cnych/drone-k8s-demo。我们将代码推送到我们自己的 GitLab 仓库上去,地址:http://git.k8s.local/course/devops-demo,这样让 Jenkins 和 Gitlab 去进行连接进行 CI/CD

[root@node1 ~]# git clone https://github.com/cnych/drone-k8s-demo.git
正克隆到 'drone-k8s-demo'...
remote: Enumerating objects: 166, done.
remote: Total 166 (delta 0), reused 0 (delta 0), pack-reused 166
接收对象中: 100% (166/166), 22.18 KiB | 0 bytes/s, done.
处理 delta 中: 100% (95/95), done.
[root@node1 ~]# ls
anaconda-ks.cfg  drone-k8s-demo  initial-setup-ks.cfg  volume.yaml  公共  模板  视频  图片  文档  下载  音乐  桌面
[root@node1 ~]# cd drone-k8s-demo/
[root@node1 drone-k8s-demo]# ls
Dockerfile  go.mod  go.sum  helm  main.go  README.md

在这里插入图片描述
在这里插入图片描述
注意这里组名应该是course
在这里插入图片描述
注意这里项目名改成devops-demo
在这里插入图片描述
在这里插入图片描述

[root@node2 drone-k8s-demo]# git remote add origin git@git.k8s.local:cource/devops-demo.git
fatal: 远程 origin 已经存在。
[root@node2 drone-k8s-demo]# git remote remove origin
[root@node2 drone-k8s-demo]# git remote add origin git@git.k8s.local:cource/devops.demo.git
[root@node2 drone-k8s-demo]# git push -u origin master
Counting objects: 166, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (70/70), done.
Writing objects: 100% (166/166), 22.18 KiB | 0 bytes/s, done.
Total 166 (delta 95), reused 166 (delta 95)
remote: Resolving deltas: 100% (95/95), done.
To git@git.k8s.local:cource/devops.demo.git
 * [new branch]      master -> master
分支 master 设置为跟踪来自 origin 的远程分支 master。

在这里插入图片描述
对于这些问题,一般是由于资源紧张引起,实际查看自愿一切正常,删除这些evite的pod就行,有时候可能需要重启下gitlab,就是删除pending那个pod

[root@master1 ~]# for i in $(kubectl get pod -n kube-ops | awk -F " " '{print $1}' | grep '^gitlab' | grep -v 'p9gpj$');do kubectl delete pod -n kube-ops $i ;done
pod "gitlab-68d5dd6bf6-4nktt" deleted
pod "gitlab-68d5dd6bf6-5dtm8" deleted
pod "gitlab-68d5dd6bf6-5l4jh" deleted
pod "gitlab-68d5dd6bf6-897vk" deleted
pod "gitlab-68d5dd6bf6-b2lgl" deleted
pod "gitlab-68d5dd6bf6-brt4k" deleted
pod "gitlab-68d5dd6bf6-j4dfd" deleted
pod "gitlab-68d5dd6bf6-j4rtx" deleted
pod "gitlab-68d5dd6bf6-jkn76" deleted
pod "gitlab-68d5dd6bf6-l2bln" deleted
pod "gitlab-68d5dd6bf6-ld8hd" deleted
pod "gitlab-68d5dd6bf6-lxgp9" deleted
pod "gitlab-68d5dd6bf6-nln4n" deleted
pod "gitlab-68d5dd6bf6-qjtcn" deleted
pod "gitlab-68d5dd6bf6-qvqrp" deleted
pod "gitlab-68d5dd6bf6-rw9st" deleted
pod "gitlab-68d5dd6bf6-sprft" deleted
pod "gitlab-68d5dd6bf6-vgq97" deleted
pod "gitlab-68d5dd6bf6-wrjhf" deleted
pod "gitlab-68d5dd6bf6-xrgt2" deleted
pod "gitlab-68d5dd6bf6-zn7bg" deleted

如果按照之前的示例,我们是不是应该像这样来编写 Pipeline 脚本:

第一步,clone 代码 第二步,进行测试,如果测试通过了才继续下面的任务 第三步,由于 Dockerfile 基本上都是放入源码中进行管理的,所以我们这里就是直接构建 Docker 镜像了 第四步,镜像打包完成,就应该推送到镜像仓库中吧 第五步,镜像推送完成,是不是需要更改 YAML 文件中的镜像 TAG 为这次镜像的 TAG 第六步,万事俱备,只差最后一步,使用 kubectl 命令行工具进行部署了

到这里我们的整个 CI/CD 的流程是不是就都完成了。我们同样可以用上面的我们自定义的一个 jnlp 的镜像来完成我们的整个构建工作,但是我们这里的项目是 golang 代码的,构建需要相应的环境,如果每次需要特定的环境都需要重新去定制下镜像这未免太麻烦了,我们这里来采用一种更加灵活的方式,自定义 podTemplate。我们可以直接在 Pipeline 中去自定义 Slave Pod 中所需要用到的容器模板,这样我们需要什么镜像只需要在 Slave Pod Template 中声明即可,完全不需要去定义一个庞大的 Slave 镜像了。

这里我们需要使用到 gitlab 的插件,用于 Gitab 侧代码变动后触发 Jenkins 的构建任务:
在这里插入图片描述
然后新建一个名为 devops-demo 类型为流水线的任务,在 构建触发器 区域选择 Build when a change is pushed to GitLab,后面的 http://jenkins.k8s.local/project/devops-demo 是我们需要在 Gitlab 上配的 Webhook 地址:
在这里插入图片描述
其中Comment (regex) for triggering a build是说在 git 仓库,发送包含 jenkins build 这样的关键字的时候会触发执行此 build 构建。然后点击下面的高级可以生成 token。这里的 url 和 token 是 jenkins 的 api,可以提供给 GtiLab 使用,在代码合并/提交commit/push代码等操作时,通知 Jenkins 执行 build 操作。
在这里插入图片描述
在这里插入图片描述
注: 复制出 URL 和 Token,我们后面配置 Gitlab 的 Webhook 会用到。

自定义一个token:
在这里插入图片描述

webhook:http://jenkins.k8s.local/project/devops-demo
secret token:cafc9ad8f3874442d5e6fec5fb6221e4
身份验证令牌:http://jenkins.k8s.local/job/devops-demo/build?token=server321

然后在下面的流水线区域我们可以选择 Pipeline script 然后在下面测试流水线脚本,我们这里选择 Pipeline script from SCM,意思就是从代码仓库中通过 Jenkinsfile 文件获取 Pipeline script 脚本定义,然后选择 SCM 来源为 Git,在出现的列表中配置上仓库地址 http://git.k8s.local/course/devops-demo.git,由于我们是在一个 Slave Pod 中去进行构建,所以如果使用 SSH 的方式去访问 Gitlab 代码仓库的话就需要频繁的去更新 SSH-KEY,所以我们这里采用直接使用用户名和密码的形式来方式:
在这里插入图片描述
我们可以看到有一个明显的错误 Could not resolve host: git.k8s.local 提示不能解析我们的 GitLab 域名,这是因为我们的域名都是自定义的,我们可以通过在 CoreDNS 中添加自定义域名解析来解决这个问题(如果你的域名是外网可以正常解析的就不会出现这个问题了):

kubectl edit cm coredns -n kube-system
# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
data:
  Corefile: |
    .:53 {
   
   
        errors
        health {
   
   
           lameduck 5s
        }
        ready
        hosts {
   
   
          192.168.23.199 git.k8s.local
          192.168.23.199 jenkins.k8s.local
          192.168.23.199 harbor.k8s.local
          fallthrough
        }
        kubernetes cluster.local in-addr.arpa ip6.arpa {
   
   
           pods insecure
           fallthrough in-addr.arpa ip6.arpa
           ttl 30
        }
        prometheus :9153
        forward . /etc/resolv.conf {
   
   
           max_concurrent 1000
        }
        cache 30
        loop
        reload
        loadbalance
    }
kind: ConfigMap
metadata:
  creationTimestamp: "2022-05-15T17:02:51Z"
  name: coredns

在这里插入图片描述
注意这里的ip全是199才对,就是我用来解析这三个域名的ingress所在的节点的ip都是192.168.23.199

修改完成后,隔一小会儿,CoreDNS 就会自动热加载(cm,volume),我们就可以在集群内访问我们自定义的域名了。然后肯定没有权限,所以需要配置帐号认证信息。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
然后需要我们配置用于构建的分支,如果所有的分支我们都想要进行构建的话,只需要将 Branch Specifier 区域留空即可,一般情况下不同的环境对应的分支才需要构建,比如 master、dev、test 等,平时开发的 feature 或者 bugfix 的分支没必要频繁构建,我们这里就只配置 master 个分支用于构建。

最后点击保存,至此,Jenkins 的持续集成配置好了,还需要配置 Gitlab 的 Webhook,用于代码提交通知 Jenkins。前往 Gitlab 中配置项目 devops-demo 的 Webhook,settings -> Webhooks,填写上面得到的 trigger 地址:
在这里插入图片描述
进入该porject或者说该pipeline下
在这里插入图片描述
在这里插入图片描述
我们这里都是自定义的域名,也没有配置 https 服务,所以记得取消配置下面的 启用SSL验证。

保存后,如果出现 Urlis blocked: Requests to the local network are not allowed 这样的报警信息,则需要进入 GitLab Admin -> 设置 -> 网络 -> 勾选 外发请求,然后保存配置。
在这里插入图片描述
现在就可以正常保存了(最好重新走一遍webhook配置),可以直接点击 测试 -> Push Event 测试是否可以正常访问 Webhook 地址,出现了 Hook executed successfully: HTTP 200 则证明 Webhook 配置成功了,否则就需要检查下 Jenkins 的安全配置是否正确了。
在这里插入图片描述
在这里插入图片描述
由于当前项目中还没有 Jenkinsfile 文件,所以触发过后会构建失败,接下来我们直接在代码仓库根目录下面添加 Jenkinsfile 文件(jenkins的流水线设置制定了使用代码仓库的Jenkinsfile),用于描述流水线构建流程,整体实现流程如下图所示:
在这里插入图片描述
首先定义最简单的流程,要注意这里和前面的不同之处,这里我们使用 podTemplate 来定义不同阶段使用的容器,有哪些阶段呢?

Clone 代码 -> 单元测试 -> Golang 编译打包 -> Docker 镜像构建/推送 -> Kubectl 部署服务。

Clone 代码在默认的 Slave 容器中即可;单元测试我们这里直接忽略,有需要这个阶段的同学自己添加上即可;Golang 编译打包肯定就需要 Golang 的容器了;Docker 镜像构建/推送是不是就需要 Docker 环境了;最后的 Kubectl 更新服务是不是就需要一个有 Kubectl 的容器环境了,所以我们这里就可以很简单的定义 podTemplate 了,如下定义:

def label = "slave-${UUID.randomUUID().toString()}"

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: 'kubectl', image: 'cnych/kubectl', command: 'cat', ttyEnabled: true)
], serviceAccount: 'jenkins', volumes: [
  hostPathVolume(mountPath: '/home/jenkins/.kube', hostPath: '/root/.kube'),
  hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock')
]) {
   
   
  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"
      }
    }
  }
}

直接在 podTemplate 里面定义每个阶段需要用到的容器,volumes 里面将我们需要用到的 docker.sock 文件,需要注意的我们使用的 label 标签是是一个随机生成的,这样有一个好处就是有多个任务来的时候就可以同时构建了。正常来说我们还需要将访问集群的 kubeconfig 文件拷贝到 kubectl 容器的 ~/.kube/config 文件下面去,这样我们就可以在容器中访问 Kubernetes 集群了,但是由于我们构建是在 Slave Pod 中去构建的,Pod 就很有可能每次调度到不同的节点去,这就需要保证每个节点上有 kubeconfig 文件才能挂载成功,所以这里我们使用另外一种方式。

通过将 kubeconfig 文件通过凭证上传到 Jenkins 中,然后在 Jenkinsfile 中读取到这个文件后,拷贝到 kubectl 容器中的 ~/.kube/config 文件中,这样同样就可以正常使用 kubectl 访问集群了。在 Jenkins 页面中添加凭据,选择 Secret file 类型,然后上传 kubeconfig 文件,指定 ID 即可:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
那个文件其实无所谓,只要内容,比如你可以将~/.kube/config文件内容拷贝出来到任何文件在上传

然后在 Jenkinsfile 的 kubectl 容器中读取上面添加的 Secret file 文件,拷贝到 ~/.kube/config 即可:

stage('运行 Kubectl') {
   
   
  container('kubectl') {
   
   
    withCredentials([file(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')]) {
   
   
      echo "查看 K8S 集群 Pod 列表"
      sh "mkdir -p ~/.kube && cp ${KUBECONFIG} ~/.kube/config"
      sh "kubectl get pods"
    }
  }
}

Jenkinsfile:
def label = "slave-${UUID.randomUUID().toString()}"

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: 'kubectl', image: 'cnych/kubectl', command: 'cat', ttyEnabled: true)
], serviceAccount: 'jenkins', volumes: [
  hostPathVolume(mountPath: '/home/jenkins/.kube', hostPath: '/root/.kube'),
  hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock')
]) {
   
   
  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(
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿白,

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值