k8s部署jenkins以及基本使用

一:Jenkins+Kubernetes方案简介

1.1、传统Jenkins部署的问题

Jenkins 是开源的一套持续集成框架,可以进行大规模的编译、测试和发布的工作,给软件开发团队带来极大的便利性。 Jenkins 的持续集成环境可以是集群化的,主要的运行模式为master-slave模式。 Jenkins 的master为Jenkins系统的控制节点,slave节点负责具体的项目编译测试等工作。 由于大公司里面需要进行编译的工程或者对象非常庞大,因此需要大量的物理节点作为slave,而且这些环节相对固定,可能很难适应其他项目的编译测试,一旦salve节点遭到破坏,需要人为的进行修复甚至重建,非常麻烦。

2.2、与Kubernetes-plugin的结合

Docker的问世,为我们提供了解决方案,使用docker作为jenkins slave节点可以解决slave节点遭到破坏后遇到的问题, 而且大量的工程不是每时每刻同时运行的因此可以在需要时吧docker拉起来进行编译测试,这样就节约了大量的物理节点, 解决了上述问题。 然而使用docker 同样需要集群,因此要用到集群管理工具, swarm 或者kubernetes。Swarm 一般用在小集群上,而且swarm和docker本身的借口完全一致, 因此这里就简单介绍单点docker节点作为slave。 Kubernetes作为大的docker 集群管理工具,当jenkins的工程数量非常大的时候可以用kubernetes, kubernetes的运维相对比docker 和swarm的门槛要高一点。 Jenkins master也可以使用docker来运行或者使用kubernetes来提供高可用的jenkins master。 Kubernetes 主要是用来进行docker 调度运行的,同样因为这一点通过kubernetes插件的配置,不需要管slave节点是否在线,因为kubernetes帮你负责创建和连接。这样就可以做到没有任务的时候没有任务slave节点在线,做到完全的按需启动slave和调度slave。

二、在K8S上构建Jenkins Server

2.1 创建PVC存储

首先需要对jenkins-server创建一个Kubernetes持久化存储卷。为了让Jenkins Server可以具有Fail Over的能力,建议将Jenkins的数据存储到远程存储或者分布式存储上。本文使用分布式存储ceph来模拟存储效果,分布式存储的部署请参考我的《rook+ceph部署分布式文件系统》,指定rook-ceph-block并分配200G空间。

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: cicdpvc
  namespace: cicd
  labels:
    app: cicd
spec:
  storageClassName: rook-ceph-block
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 200Gi

2.2 创建jenkins-server

创建jenkins-server的yaml如下所示

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: jenkins2
  namespace: cicd
spec:
  template:
    metadata:
      labels:
        app: jenkins2
    spec:
      terminationGracePeriodSeconds: 10
      serviceAccountName: jenkins2
      containers:
      - name: jenkins
        image: 10.57.30.23:4000/jenkins/jenkins:lts
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 8080
          name: web
          protocol: TCP
        - containerPort: 50000
          name: agent
          protocol: TCP
        resources:
          limits:
            cpu: 2000m
            memory: 4Gi
          requests:
            cpu: 1000m
            memory: 2Gi
        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
          subPath: jenkins2
          mountPath: /var/jenkins_home
        env:
        - name: LIMITS_MEMORY
          valueFrom:
            resourceFieldRef:
              resource: limits.memory
              divisor: 1Mi
        - name: JAVA_OPTS
          value: -Xmx$(LIMITS_MEMORY)m -XshowSettings:vm -Dhudson.slaves.NodeProvisioner.initialDelay=0 -Dhudson.slaves.NodeProvisioner.MARGIN=50 -Dhudson.slaves.NodeProvisioner.MARGIN0=0.85 -Duser.timezone=Asia/Shanghai
      securityContext:
        fsGroup: 1000
      volumes:
      - name: jenkinshome
        persistentVolumeClaim:
          claimName: cicdpvc
 
---
apiVersion: v1
kind: Service
metadata:
  name: jenkins2
  namespace: cicd
  labels:
    app: jenkins2
spec:
  selector:
    app: jenkins2
  ports:
  - name: web
    port: 8080
    targetPort: web
  - name: agent
    port: 50000
    targetPort: agent
 
---
 
apiVersion: v1
kind: ServiceAccount
metadata:
  name: jenkins2
  namespace: cicd
 
---
 
kind: Role
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
  name: jenkins2
  namespace: cicd
rules:
  - 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: RoleBinding
metadata:
  name: jenkins2
  namespace: cicd
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: jenkins2
subjects:
  - kind: ServiceAccount
    name: jenkins2
    namespace: cicd

运行命令完成安装。

kubectl apply -f jenkins-deployment.yaml

执行命令查看运行状态

kubectl get pod,svc,deploy -n cicd

2.3:暴露jenkins服务

这个时候管理员在外网还不能访问,所以我们用ingress方式将jenkins-server暴露出来。本文使用我之前写的《Nginx+Ingress-controller解决L7外网Web服务暴露和负载均衡》来将ingress暴露服务。

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: jenkins-ingress
  namespace: cicd
  annotations:
    kubernetes.io/ingress.class: "nginx"
spec:
  rules:
  - host: jenkins-k8s.zhytongdun.com
    http:
      paths:
      - backend:
          serviceName: jenkins2
          servicePort: 8080

执行kubectl apply -f jenkins-ingress.yaml后,执行 kubectl get ingress -n cicd查看ingress运行情况。 此时,我将通过笔记本访问,遂将jenkins-k8s.zhytongdun.com和10.57.30.23绑定在/etc/hosts上。

此时运行jenkins-k8s.zhytongdun.com,就可以看到如下欢迎界面。

2.4:初始化安装

使用kubectl exec命令进入容器,cat欢迎界面显示的路径,得到初始密码。

下一步后,有配置网络和代理,填上代理名称。

然后下一步,选择安装推荐的插件。如下图所示。 一些常用的插件已经开始安装,速度还是很快的。

最后设置完密码后就可以看到成功的界面。

三:Jenkins集成k8s

3.1 k8s插件安装

进入系统管理-插件管理-可选插件,输入kubernetes关键字,选择如下所示插件,点击直接安装。

3.2 k8s插件配置

选择系统配置-选择一个云-kubernetes

先填写jenkins和kubernetes相关配置, 注意 namespace,我们这里填 cicd,与jenkins在一个namespace下,然后点击Test Connection,如果出现 Connection test successful 的提示信息证明 Jenkins 已经可以和 Kubernetes 系统正常通信了,然后下方的 Jenkins URL 地址:http://jenkins2.cicd.svc.cluster.local:8080,这里的格式为:服务名.namespace.svc.cluster.local:8080,根据上面创建的jenkins 的服务名填写,我这里是之前创建的名为jenkins,如果是用上面我们创建的就应该是jenkins2。 接着,需要配置模板,所谓模板,其实就是配置 Jenkins Slave 运行的 Pod 模板,命名空间我们同样是用 cicd,Labels 这里也非常重要,对于后面执行 Job 的时候需要用到该值,然后我们这里使用的是 jenkins:jnlp 这个镜像,这个镜像是在官方的 jnlp 镜像基础上拉到私有仓库的,加入了 kubectl 等一些实用的工具。 另外需要注意我们这里需要在下面挂载一个主机目录,一个是 /var/run/docker.sock,该文件是用于 Pod 中的容器能够共享宿主机的 Docker,这就是大家说的 docker in docker 的方式,Docker 二进制文件我们已经打包到上面的镜像中了。如果在slave agent中想要访问kubernetes 集群中其他资源,我们还需要绑定之前创建的Service Account 账号:jenkins2。 另外还有几个参数需要注意,如下图中的Time in minutes to retain slave when idle,这个参数表示的意思是当处于空闲状态的时候保留 Slave Pod 多长时间,这个参数最好我们保存默认就行了,如果你设置过大的话,Job 任务执行完成后,对应的 Slave Pod 就不会立即被销毁删除。

到这里我们的 Kubernetes Plugin 插件就算配置完成了。

三:测试

3.1 自由风格项目

在 Jenkins 首页点击create new jobs,创建一个测试的任务,输入任务名称,然后选择自由风格项目。 在下面的 Label Expression 这里要填入haimaxy-jnlp,就是前面我们配置的 Slave Pod 中的 Label,这两个地方必须保持一致。 然后往下拉,在 Build 区域选择执行 shell。输入测试命令,并点击保存。这个测试命令就是简单在命令行打印一些内容。

echo "测试 Kubernetes 动态生成 jenkins slave"
echo "==============docker in docker==========="
docker info
 
echo "=============kubectl============="
kubectl get pods -n cicd

点击保存后,执行构建。

构建完成后,查看控制台输出。

控制台输出
Started by user zhy
Running as SYSTEM
Agent jnlp-qp97j is provisioned from template Kubernetes Pod Template
Agent specification [Kubernetes Pod Template] (haimaxy-jnlp):
* [jnlp] 10.57.30.23:4000/jenkins:jnlp
 
Building remotely on jnlp-qp97j (haimaxy-jnlp) in workspace /home/jenkins/workspace/haimaxy-jnlp-slave-demo2
[haimaxy-jnlp-slave-demo2] $ /bin/sh -xe /tmp/jenkins6427624934747148799.sh
+ echo 测试 Kubernetes 动态生成 jenkins slave
测试 Kubernetes 动态生成 jenkins slave
+ echo ==============docker in docker===========
==============docker in docker===========
+ docker info
Containers: 52
 Running: 38
 Paused: 0
 Stopped: 14
Images: 42
Server Version: 18.09.4
Storage Driver: overlay2
 Backing Filesystem: xfs
 Supports d_type: true
 Native Overlay Diff: true
Logging Driver: json-file
Cgroup Driver: cgroupfs
Plugins:
 Volume: local
 Network: bridge host macvlan null overlay
 Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
Swarm: inactive
Runtimes: runc
Default Runtime: runc
Init Binary: docker-init
containerd version: bb71b10fd8f58240ca47fbb579b9d1028eea7c84
runc version: 2b18fe1d885ee5083ef9f0838fee39b62d653e30
init version: fec3683
Security Options:
 seccomp
  Profile: default
Kernel Version: 3.10.0-957.12.2.el7.x86_64
Operating System: CentOS Linux 7 (Core)
OSType: linux
Architecture: x86_64
CPUs: 24
Total Memory: 94.21GiB
Name: dataocean-d-030022.te.td
ID: GHU3:XMJG:C6HK:ZPNE:O2VC:GWAN:WFAB:7CHV:B6KP:AQST:PQ3S:7AK7
Docker Root Dir: /var/lib/docker
Debug Mode (client): false
Debug Mode (server): false
Registry: https://index.docker.io/v1/
Labels:
Experimental: false
Insecure Registries:
 10.57.30.23:4000
 127.0.0.0/8
Live Restore Enabled: false
 
+ echo =============kubectl=============
=============kubectl=============
+ kubectl get pods -n cicd
NAME                        READY     STATUS    RESTARTS   AGE
jenkins2-564659b655-7z9vx   1/1       Running   0          2h
jnlp-qp97j                  1/1       Running   0          12s
Finished: SUCCESS

在运行过程中,执行kubectl查看命令,可以看到在构建生命周期过程中,pod的变化。

3.2 工作流项目

在 Jenkins 首页点击create new jobs,创建一个测试的任务,输入任务名称,然后选择流水线。