Jenkins 基于 Kubernetes 的动态和静态节点

WilliamGates
发布于 2023-11-23 14:33
浏览
0收藏

提到基于 Kubernetes 的 CI/CD,可以使用的工具有很多,比如 Jenkins、Gitlab CI、Drone 之类的,我们这里会使用大家最为熟悉的 Jenkins 来做 CI/CD 的工具。

Jenkins 基于 Kubernetes 的动态和静态节点-鸿蒙开发者社区

安装

既然要基于 Kubernetes 来做 CI/CD,我们这里最好还是将 Jenkins 安装到 Kubernetes 集群当中,安装的方式也很多,我们这里仍然还是使用手动的方式,这样可以了解更多细节,对应的资源清单文件如下所示:

# jenkins.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: jenkins-local
  labels:
    app: jenkins
spec:
  accessModes:
    - ReadWriteOnce
  capacity:
    storage: 5Gi
  storageClassName: local-storage
  local:
    path: /data/k8s/jenkins
  persistentVolumeReclaimPolicy: Retain
  nodeAffinity:
    required:
      nodeSelectorTerms:
        - matchExpressions:
            - key: kubernetes.io/hostname
              operator: In
              values:
                - node2
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: jenkins-pvc
  namespace: kube-ops
spec:
  storageClassName: local-storage
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: jenkins
  namespace: kube-ops
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: jenkins
rules:
  - apiGroups: ["extensions", "apps"]
    resources: ["deployments", "ingresses"]
    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", "events"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: jenkins
  namespace: kube-ops
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: jenkins
subjects:
  - kind: ServiceAccount
    name: jenkins
    namespace: kube-ops
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: jenkins
  namespace: kube-ops
spec:
  selector:
    matchLabels:
      app: jenkins
  template:
    metadata:
      labels:
        app: jenkins
    spec:
      serviceAccount: jenkins
      initContainers:
        - name: fix-permissions
          image: busybox:1.35.0
          command: ["sh", "-c", "chown -R 1000:1000 /var/jenkins_home"]
          securityContext:
            privileged: true
          volumeMounts:
            - name: jenkinshome
              mountPath: /var/jenkins_home
      containers:
        - name: jenkins
          image: jenkins/jenkins:lts-jdk11
          imagePullPolicy: IfNotPresent
          env:
            - name: JAVA_OPTS
              value: -Dhudson.model.DownloadService.noSignatureCheck=true
          ports:
            - containerPort: 8080
              name: web
              protocol: TCP
            - containerPort: 50000
              name: agent
              protocol: TCP
          readinessProbe:
            httpGet:
              path: /login
              port: 8080
            initialDelaySeconds: 60
            timeoutSeconds: 5
            failureThreshold: 12
          volumeMounts:
            - name: jenkinshome
              mountPath: /var/jenkins_home
      volumes:
        - name: jenkinshome
          persistentVolumeClaim:
            claimName: jenkins-pvc
---
apiVersion: v1
kind: Service
metadata:
  name: jenkins
  namespace: kube-ops
  labels:
    app: jenkins
spec:
  selector:
    app: jenkins
  ports:
    - name: web
      port: 8080
      targetPort: web
    - name: agent
      port: 50000
      targetPort: agent
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: jenkins
  namespace: kube-ops
spec:
  ingressClassName: nginx
  rules:
    - host: jenkins.k8s.local
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: jenkins
                port:
                  name: web

我们这里使用 ​​jenkins/jenkins:lts-jdk11​​​ 镜像,这是 jenkins 官方的 Docker 镜像,然后也有一些环境变量,当然我们也可以根据自己的需求来定制一个镜像,比如我们可以将一些插件打包在自定义的镜像当中,可以参考文档:https://github.com/jenkinsci/docker,我们这里使用默认的官方镜像就行,另外一个还需要注意的数据的持久化,将容器的 ​​/var/jenkins_home​​ 目录持久化即可,我们这里使用的是 Local PV 的方式。

由于我们这里使用的镜像内部运行的用户 ​​uid=1000​​​,所以我们这里挂载出来后会出现权限问题,为解决这个问题,我们同样还是用一个简单的 ​​initContainer​​ 来修改下我们挂载的数据目录。

另外由于 jenkens 会对 ​​update-center.json​​​ 做签名校验安全检查,这里我们需要先提前关闭,否则下面更改插件源可能会失败,通过配置环境变量 ​​JAVA_OPTS=-Dhudson.model.DownloadService.noSignatureCheck=true​​ 即可。

另外我们这里还需要使用到一个拥有相关权限的 ​​serviceAccount:jenkins​​​,我们这里只是给 jenkins 赋予了一些必要的权限,当然如果你对 serviceAccount 的权限不是很熟悉的话,我们给这个 sa 绑定一个 ​​cluster-admin​​ 的集群角色权限也是可以的,当然这样具有一定的安全风险。最后就是通过 Ingress 来暴露我们的服务,这个比较简单。

我们直接来创建 jenkins 的资源清单即可:

$ kubectl apply -f jenkins.yaml
$ kubectl get pods -n kube-ops -l app=jenkins
NAME                       READY   STATUS    RESTARTS   AGE
jenkins-55c4676f4d-fhmw2   1/1     Running   0          3m5s
$ kubectl logs -f jenkins-55c4676f4d-fhmw2 -n kube-ops
Running from: /usr/share/jenkins/jenkins.war
webroot: /var/jenkins_home/war
# ......
2023-09-07 06:56:26.123+0000 [id=33]    INFO    jenkins.install.SetupWizard#init:

*************************************************************
*************************************************************
*************************************************************

Jenkins initial setup is required. An admin user has been created and a password generated.
Please use the following password to proceed to installation:

278e9dcdcab04d11ae671f7f81f517ba

This may also be found at: /var/jenkins_home/secrets/initialAdminPassword

*************************************************************
*************************************************************
*************************************************************

2023-09-07 06:56:38.572+0000 [id=29]    INFO    jenkins.InitReactorRunner$1#onAttained: Completed initialization
2023-09-07 06:56:38.583+0000 [id=23]    INFO    hudson.lifecycle.Lifecycle#onReady: Jenkins is fully up and running
2023-09-07 06:57:02.555+0000 [id=49]    INFO    h.m.DownloadService$Downloadable#load: Obtained the updated data file for hudson.tasks.Maven.MavenInstaller
2023-09-07 06:57:02.556+0000 [id=49]    INFO    hudson.util.Retrier#start: Performed the action check updates server successfully at the attempt #1

看到上面的 ​​run: Jenkins is fully up and running​​ 信息就证明我们的 Jenkins 应用以前启动起来了。

然后我们可以通过 Ingress 中定义的域名 ​​jenkins.k8s.local​​​(需要做 DNS 解析或者在本地 ​​/etc/hosts​​ 中添加映射)来访问 jenkins 服务:

Jenkins 基于 Kubernetes 的动态和静态节点-鸿蒙开发者社区

然后可以执行下面的命令获取解锁的管理员密码:

$ kubectl exec -it jenkins-55c4676f4d-fhmw2 -n kube-ops -- cat /var/jenkins_home/secrets/initialAdminPassword
278e9dcdcab04d11ae671f7f81f517ba   # jenkins启动日志里面也有

然后跳过插件安装,选择默认安装插件过程会非常慢(也可以选择安装推荐的插件),点击右上角关闭选择插件。

Jenkins 基于 Kubernetes 的动态和静态节点-鸿蒙开发者社区

跳过后会直接进入 Jenkins 就绪页面,直接点击开始使用即可:

Jenkins 基于 Kubernetes 的动态和静态节点-鸿蒙开发者社区

然后就可以进入 Jenkins 主页了。

Jenkins 基于 Kubernetes 的动态和静态节点-鸿蒙开发者社区

首先安装中文插件(如果想要中文界面的话),搜索 ​Localization: Chinese​

Jenkins 基于 Kubernetes 的动态和静态节点-鸿蒙开发者社区

安装重启完成后,会自动跳转到登录页面:

Jenkins 基于 Kubernetes 的动态和静态节点-鸿蒙开发者社区

这里还是使用 admin 和前面的初始密码进行登录。然后可以进入用户管理页面 http://jenkins.k8s.local/user/admin/configure 修改用户密码:

Jenkins 基于 Kubernetes 的动态和静态节点-鸿蒙开发者社区

然后就可以使用新的密码登录了。

接下来我们可以安装其他需要的插件,比如 ​​Pipeline​​​ 插件。​​Pipeline​​ 是 Jenkins 的一个核心插件,它定义了一套 DSL 语言,可以用来编写 Pipeline 脚本,这个脚本可以实现从代码构建到部署的整个流程。在使用 Pipeline 类型的项目时,需要提前安装 Jenkins 的 Pipeline 插件。

Jenkins 基于 Kubernetes 的动态和静态节点-鸿蒙开发者社区

安装好插件后新建一个 Pipeline 类型的作业:

Jenkins 基于 Kubernetes 的动态和静态节点-鸿蒙开发者社区

自由风格项目和 Pipeline 类型的项目区别是,构建部分的操作都是在页面上面完成的。Pipeline 的构建任务描述都是通过代码的方式。

Jenkins 基于 Kubernetes 的动态和静态节点-鸿蒙开发者社区

保存后我们可以点击立即构建执行这个任务,也可以查看这个任务的执行结果输出:

Jenkins 基于 Kubernetes 的动态和静态节点-鸿蒙开发者社区

架构

Jenkins 安装完成了,接下来我们不用急着就去使用,我们要了解下在 Kubernetes 环境下面使用 Jenkins 有什么好处。

我们知道持续构建与发布是我们日常工作中必不可少的一个步骤,目前大多公司都采用 Jenkins 集群来搭建符合需求的 CI/CD 流程,然而传统的 Jenkins Slave 一主多从方式会存在一些痛点,比如:

  • 主 Master 发生单点故障时,整个流程都不可用了
  • 每个 Slave 的配置环境不一样,来完成不同语言的编译打包等操作,但是这些差异化的配置导致管理起来非常不方便,维护起来也是比较费劲
  • 资源分配不均衡,有的 Slave 要运行的 job 出现排队等待,而有的 Slave 处于空闲状态
  • 资源有浪费,每台 Slave 可能是物理机或者虚拟机,当 Slave 处于空闲状态时,也不会完全释放掉资源。

正因为上面的这些种种痛点,我们渴望一种更高效更可靠的方式来完成这个 CI/CD 流程,而 Docker 虚拟化容器技术能很好的解决这个痛点,又特别是在 Kubernetes 集群环境下面能够更好来解决上面的问题,下图是基于 Kubernetes 搭建 Jenkins 集群的简单示意图:

Jenkins 基于 Kubernetes 的动态和静态节点-鸿蒙开发者社区

从图上可以看到 ​​Jenkins Master​​​ 和 ​​Jenkins Slave​​ 以 Pod 形式运行在 Kubernetes 集群的 Node 上,Master 运行在其中一个节点,并且将其配置数据存储到一个 Volume 上去,Slave 运行在各个节点上,并且它不是一直处于运行状态,它会按照需求动态的创建并自动删除。

这种方式的工作流程大致为:当 Jenkins Master 接受到 Build 请求时,会根据配置的 Label 动态创建一个运行在 Pod 中的 Jenkins Slave 并注册到 Master 上,当运行完 Job 后,这个 Slave 会被注销并且这个 Pod 也会自动删除,恢复到最初状态。

那么我们使用这种方式带来了哪些好处呢?

  • 服务高可用,当 Jenkins Master 出现故障时,Kubernetes 会自动创建一个新的 Jenkins Master 容器,并且将 Volume 分配给新创建的容器,保证数据不丢失,从而达到集群服务高可用。
  • 动态伸缩,合理使用资源,每次运行 Job 时,会自动创建一个 Jenkins Slave,Job 完成后,Slave 自动注销并删除容器,资源自动释放,而且 Kubernetes 会根据每个资源的使用情况,动态分配 Slave 到空闲的节点上创建,降低出现因某节点资源利用率高,还排队等待在该节点的情况。
  • 扩展性好,当 Kubernetes 集群的资源严重不足而导致 Job 排队等待时,可以很容易的添加一个 Kubernetes Node 到集群中,从而实现扩展。 是不是以前我们面临的种种问题在 Kubernetes 集群环境下面是不是都没有了啊?看上去非常完美。


文章转载自公众号:k8s技术圈

分类
标签
已于2023-11-23 14:33:07修改
收藏
回复
举报
回复
    相关推荐