侧边栏壁纸
博主头像
落叶人生博主等级

走进秋风,寻找秋天的落叶

  • 累计撰写 130562 篇文章
  • 累计创建 28 个标签
  • 累计收到 9 条评论
标签搜索

目 录CONTENT

文章目录

基于 Kubernete、Jenkins 的 CI/CD(一)

2022-07-11 星期一 / 0 评论 / 0 点赞 / 48 阅读 / 27141 字

提到基于Kubernete的CI/CD,可以使用的工具有很多,比如Jenkins、Gitlab CI已经新兴的drone之类的,我们这里会使用大家最为熟悉的Jenkins来做CI/CD的工具,本教程基

提到基于KuberneteCI/CD,可以使用的工具有很多,比如JenkinsGitlab CI已经新兴的drone之类的,我们这里会使用大家最为熟悉的Jenkins来做CI/CD的工具,本教程基于k8s 1.16.1

创建PVC

PVC 的全称是:PersistentVolumeClaim(持久化卷声明),PVC 是用户存储的一种声明,PVC 和 Pod 比较类似,Pod 消耗的是节点,PVC 消耗的是 PV 资源,Pod 可以请求 CPU 和内存,而 PVC 可以请求特定的存储空间和访问模式。对于真正使用存储的用户不需要关心底层的存储实现细节,只需要直接使用 PVC 即可。

但是通过 PVC 请求到一定的存储空间也很有可能不足以满足应用对于存储设备的各种需求,而且不同的应用程序对于存储性能的要求可能也不尽相同,比如读写速度、并发性能等,为了解决这一问题,Kubernetes 又为我们引入了一个新的资源对象:StorageClass,通过 StorageClass 的定义,管理员可以将存储资源定义为某种类型的资源,比如快速存储、慢速存储等,用户根据 StorageClass 的描述就可以非常直观的知道各种存储资源的具体特性了,这样就可以根据应用的特性去申请合适的存储资源了。

NFS

我们这里为了演示方便,决定使用相对简单的 NFS 这种存储资源,接下来我们在节点192.168.56.100上来安装 NFS 服务,数据目录:/data/k8s/

  1. 关闭防火墙

    $ systemctl stop firewalld.service$ systemctl disable firewalld.service
  2. 安装配置 nfs

    $ yum -y install nfs-utils rpcbind

共享目录设置权限:

$ mkdir -p /data/k8s/$ chmod 755 /data/k8s/

配置 nfs,nfs 的默认配置文件在 /etc/exports 文件下,在该文件中添加下面的配置信息:

$ vi /etc/exports/data/k8s  *(rw,sync,no_root_squash)

配置说明:

  • /data/k8s:是共享的数据目录
  • *:表示任何人都有权限连接,当然也可以是一个网段,一个 IP,也可以是域名
  • rw:读写的权限
  • sync:表示文件同时写入硬盘和内存
  • no_root_squash:当登录 NFS 主机使用共享目录的使用者是 root 时,其权限将被转换成为匿名使用者,通常它的 UID 与 GID,都会变成 nobody 身份

当然 nfs 的配置还有很多,感兴趣的同学可以在网上去查找一下。

  1. 启动服务 nfs 需要向 rpc 注册,rpc 一旦重启了,注册的文件都会丢失,向他注册的服务都需要重启
.

注意启动顺序,先启动 rpcbind

.
$ systemctl start rpcbind.service$ systemctl enable rpcbind$ systemctl status rpcbind● rpcbind.service - RPC bind service   Loaded: loaded (/usr/lib/systemd/system/rpcbind.service; disabled; vendor preset: enabled)   Active: active (running) since Tue 2018-07-10 20:57:29 CST; 1min 54s ago  Process: 17696 ExecStart=/sbin/rpcbind -w $RPCBIND_ARGS (code=exited, status=0/SUCCESS) Main PID: 17697 (rpcbind)    Tasks: 1   Memory: 1.1M   CGroup: /system.slice/rpcbind.service           └─17697 /sbin/rpcbind -wJul 10 20:57:29 master systemd[1]: Starting RPC bind service...Jul 10 20:57:29 master systemd[1]: Started RPC bind service.

看到上面的 Started 证明启动成功了。

然后启动 nfs 服务:

$ systemctl start nfs.service$ systemctl enable nfs$ systemctl status nfs● nfs-server.service - NFS server and services   Loaded: loaded (/usr/lib/systemd/system/nfs-server.service; enabled; vendor preset: disabled)  Drop-In: /run/systemd/generator/nfs-server.service.d           └─order-with-mounts.conf   Active: active (exited) since Tue 2018-07-10 21:35:37 CST; 14s ago Main PID: 32067 (code=exited, status=0/SUCCESS)   CGroup: /system.slice/nfs-server.serviceJul 10 21:35:37 master systemd[1]: Starting NFS server and services...Jul 10 21:35:37 master systemd[1]: Started NFS server and services.

同样看到 Started 则证明 NFS Server 启动成功了。

另外我们还可以通过下面的命令确认下:

$ rpcinfo -p|grep nfs    100003    3   tcp   2049  nfs    100003    4   tcp   2049  nfs    100227    3   tcp   2049  nfs_acl    100003    3   udp   2049  nfs    100003    4   udp   2049  nfs    100227    3   udp   2049  nfs_acl

查看具体目录挂载权限:

$ cat /var/lib/nfs/etab/data/k8s    *(rw,sync,wdelay,hide,nocrossmnt,secure,no_root_squash,no_all_squash,no_subtree_check,secure_locks,acl,no_pnfs,anonuid=65534,anongid=65534,sec=sys,secure,no_root_squash,no_all_squash)

到这里我们就把 nfs server 给安装成功了,接下来我们在节点10.151.30.62(换成你的ip)上来安装 nfs 的客户端来验证下 nfs

  1. 安装 nfs 当前也需要先关闭防火墙:
    $ systemctl stop firewalld.service$ systemctl disable firewalld.service

然后安装 nfs

$ yum -y install nfs-utils rpcbind

安装完成后,和上面的方法一样,先启动 rpc、然后启动 nfs:

$ systemctl start rpcbind.service $ systemctl enable rpcbind.service $ systemctl start nfs.service    $ systemctl enable nfs.service
  1. 挂载数据目录 客户端启动完成后,我们在客户端来挂载下 nfs 测试下:

首先检查下 nfs 是否有共享目录:

$ showmount -e 192.168.56.100Export list for 192.168.56.100:/data/k8s *

然后我们在客户端上新建目录:

$ mkdir -p /root/course/kubeadm/data

将 nfs 共享目录挂载到上面的目录:

$ mount -t nfs 192.168.56.100:/data/k8s /root/course/kubeadm/data

挂载成功后,在客户端上面的目录中新建一个文件,然后我们观察下 nfs 服务端的共享目录下面是否也会出现该文件:

$ touch /root/course/kubeadm/data/test.txt

然后在 nfs 服务端查看:

$ ls -ls /data/k8s/total 44 -rw-r--r--. 1 root root 4 Jul 10 21:50 test.txt

如果上面出现了 test.txt 的文件,那么证明我们的 nfs 挂载成功了。

PV

有了上面的 NFS 共享存储,下面我们就可以来使用 PV 和 PVC 了。PV 作为存储资源,主要包括存储能力、访问模式、存储类型、回收策略等关键信息,下面我们来新建一个 PV 对象,使用 nfs 类型的后端存储,1G 的存储空间,访问模式为 ReadWriteOnce,回收策略为 Recyle,对应的 YAML 文件如下:(pv1-demo.yaml)

apiVersion: v1kind: PersistentVolumemetadata:  name:  pv1spec:  capacity:     storage: 1Gi  accessModes:  - ReadWriteOnce  persistentVolumeReclaimPolicy: Recycle  nfs:    path: /data/k8s    server: 192.168.56.100

Kubernetes 支持的 PV 类型有很多,比如常见的 Ceph、GlusterFs、NFS,甚至 HostPath也可以,不过 HostPath 我们之前也说过仅仅可以用于单机测试,更多的支持类型可以前往 Kubernetes PV 官方文档进行查看,因为每种存储类型都有各自的特点,所以我们在使用的时候可以去查看相应的文档来设置对应的参数。

然后同样的,直接使用 kubectl 创建即可:

$ kubectl create -f pv1-demo.yamlpersistentvolume "pv1" created$ kubectl get pvNAME      CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM               STORAGECLASS   REASON    AGEpv1       1Gi        RWO            Recycle          Available                                                12s

我们可以看到 pv1 已经创建成功了,状态是 Available,表示 pv1 就绪,可以被 PVC 申请。我们来分别对上面的属性进行一些解读。

基于K8s安装部署Jenkins

听我们课程的大部分同学应该都或多或少的听说过Jenkins,我们这里就不再去详细讲述什么是 Jenkins 了,直接进入正题,后面我们会单独的关于 Jenkins 的学习课程,想更加深入学习的同学也可以关注下。既然要基于Kubernetes来做CI/CD,当然我们这里需要将 Jenkins 安装到 Kubernetes 集群当中,新建一个 Deployment:(jenkins2.yaml)

---apiVersion: apps/v1kind: Deploymentmetadata:  name: jenkins2  namespace: kube-opsspec:  selector:    matchLabels:      app: jenkins2  template:    metadata:      labels:        app: jenkins2    spec:      terminationGracePeriodSeconds: 10      serviceAccount: jenkins2      containers:      - name: jenkins        image: jenkins/jenkins:lts        imagePullPolicy: IfNotPresent        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          subPath: jenkins2          mountPath: /var/jenkins_home      securityContext:        fsGroup: 1000      volumes:      - name: jenkinshome        persistentVolumeClaim:          claimName: opspvc---apiVersion: v1kind: Servicemetadata:  name: jenkins2  namespace: kube-ops  labels:    app: jenkins2spec:  selector:    app: jenkins2  type: NodePort  ports:  - name: web    port: 8080    targetPort: web    nodePort: 30002  - name: agent    port: 50000    targetPort: agent

为了方便演示,我们把本节课所有的对象资源都放置在一个名为 kube-ops 的 namespace 下面,所以我们需要添加创建一个 namespace:

$ kubectl create namespace kube-ops

我们这里使用一个名为 jenkins/jenkins:lts 的镜像,这是 jenkins 官方的 Docker 镜像,然后也有一些环境变量,当然我们也可以根据自己的需求来定制一个镜像,比如我们可以将一些插件打包在自定义的镜像当中,可以参考文档:github.com/jenkinsci/d…,我们这里使用默认的官方镜像就行,另外一个还需要注意的是我们将容器的 /var/jenkins_home 目录挂载到了一个名为 opspvc 的 PVC 对象上面,所以我们同样还得提前创建一个对应的 PVC 对象,当然我们也可以使用我们前面的 StorageClass 对象来自动创建:(pvc.yaml)

apiVersion: v1kind: PersistentVolumemetadata:  name: opspvspec:  capacity:    storage: 20Gi  accessModes:  - ReadWriteMany  persistentVolumeReclaimPolicy: Delete  nfs:    server: 192.168.56.100    path: /data/k8s---kind: PersistentVolumeClaimapiVersion: v1metadata:  name: opspvc  namespace: kube-opsspec:  accessModes:    - ReadWriteMany  resources:    requests:      storage: 20Gi

创建需要用到的 PVC 对象:

$ kubectl create -f pvc.yaml

另外我们这里还需要使用到一个拥有相关权限的 serviceAccount:jenkins2,我们这里只是给 jenkins 赋予了一些必要的权限,当然如果你对 serviceAccount 的权限不是很熟悉的话,如果jenkins需要动态发布到不同命名空间,subjects要绑定不同namespace,下面我绑定了kube-ops、default,我们给这个 sa 绑定一个 cluster-admin 的集群角色权限也是可以的,当然这样具有一定的安全风险:(rbac.yaml)

apiVersion: v1kind: ServiceAccountmetadata:  name: jenkins2  namespace: kube-ops---kind: ClusterRoleapiVersion: rbac.authorization.k8s.io/v1beta1metadata:  name: jenkins2rules:  - 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/v1beta1kind: ClusterRoleBindingmetadata:  name: jenkins2  namespace: kube-opsroleRef:  apiGroup: rbac.authorization.k8s.io  kind: ClusterRole  name: jenkins2subjects:  - kind: ServiceAccount    name: jenkins2    namespace: kube-ops - kind: ServiceAccount    name: jenkins2    namespace: default

创建 rbac 相关的资源对象:

$ kubectl create -f rbac.yamlserviceaccount "jenkins2" createdrole.rbac.authorization.k8s.io "jenkins2" createdrolebinding.rbac.authorization.k8s.io "jenkins2" created

最后为了方便我们测试,我们这里通过 NodePort 的形式来暴露 Jenkins 的 web 服务,固定为30002端口,另外还需要暴露一个 agent 的端口,这个端口主要是用于 Jenkins 的 master 和 slave 之间通信使用的。

一切准备的资源准备好过后,我们直接创建 Jenkins 服务:

$ chown -R 1000 /data/k8s/jenkins2$ kubectl create -f jenkins2.yamldeployment.extensions "jenkins2" createdservice "jenkins2" created

创建完成后,要去拉取镜像可能需要等待一会儿,然后我们查看下 Pod 的状态:

$ kubectl get pods -n kube-opsNAME                        READY     STATUS    RESTARTS   AGEjenkins2-7f5494cd44-pqpzs   0/1       Running   0          2m

可以看到该 Pod 处于 Running 状态,但是 READY 值确为0,然后我们用 describe 命令去查看下该 Pod 的详细信息:

$ kubectl describe pod jenkins2-7f5494cd44-pqpzs -n kube-ops...Normal   Created                3m                kubelet, node01    Created container  Normal   Started                3m                kubelet, node01    Started container  Warning  Unhealthy              1m (x10 over 2m)  kubelet, node01    Liveness probe failed: Get http://10.244.1.165:8080/login: dial tcp 10.244.1.165:8080: getsockopt: connection refused  Warning  Unhealthy              1m (x10 over 2m)  kubelet, node01    Readiness probe failed: Get http://10.244.1.165:8080/login: dial tcp 10.244.1.165:8080: getsockopt: connection refused

可以看到上面的 Warning 信息,健康检查没有通过,具体原因是什么引起的呢?可以通过查看日志进一步了解:

$ kubectl logs -f jenkins2-7f5494cd44-pqpzs -n kube-opstouch: cannot touch '/var/jenkins_home/copy_reference_file.log': Permission deniedCan not write to /var/jenkins_home/copy_reference_file.log. Wrong volume permissions?

很明显可以看到上面的错误信息,意思就是我们没有权限在 jenkins 的 home 目录下面创建文件,这是因为默认的镜像使用的是 jenkins 这个用户,而我们通过 PVC 挂载到 nfs 服务器的共享数据目录下面却是 root 用户的,所以没有权限访问该目录,要解决该问题,也很简单,我只需要在 nfs 共享数据目录下面把我们的目录权限重新分配下即可:

$ chown -R 1000 /data/k8s/jenkins2
.

当然还有另外一种方法是我们自定义一个镜像,在镜像中指定使用 root 用户也可以

.

然后我们再重新创建:

$ kubectl delete -f jenkins.yamldeployment.extensions "jenkins2" deletedservice "jenkins2" deleted$ kubectl create -f jenkins.yamldeployment.extensions "jenkins2" createdservice "jenkins2" created

现在我们再去查看新生成的 Pod 已经没有错误信息了:

$ kubectl get pods -n kube-opsNAME                        READY     STATUS        RESTARTS   AGEjenkins2-7f5494cd44-smn2r   1/1       Running       0          25s

等到服务启动成功后,我们就可以根据任意节点的 IP:30002 端口就可以访问 jenkins 服务了,可以根据提示信息进行安装配置即可: 

1.进入jenkins系统管理 2.进入插件管理3.高级

cd {你的Jenkins工作目录}/updates #进入更新配置位置

修改default.json文件

sed -i 's/http:////updates.jenkins-ci.org//download/https:////mirrors.tuna.tsinghua.edu.cn//jenkins/g' default.json && sed -i 's/http:////www.google.com/https:////www.baidu.com/g' default.json

初始化的密码我们可以在 jenkins 的容器的日志中进行查看,也可以直接在 nfs 的共享数据目录中查看:

$ cat /data/k8s/jenkins2/secrets/initAdminPassword

然后选择安装推荐的插件即可。

注意 namespace,我们这里填 kube-ops,然后点击Test Connection,如果出现 Connection test successful 的提示信息证明 Jenkins 已经可以和 Kubernetes 系统正常通信了,然后下方的 Jenkins URL 地址:jenkins2.kube-ops.svc.cluster.local:8080,这里的格式为:服务名.namespace.svc.cluster.local:8080,根据上面创建的jenkins 的服务名填写,我这里是之前创建的名为jenkins,如果是用上面我们创建的就应该是jenkins2

.

另外需要注意,如果这里 Test Connection 失败的话,很有可能是权限问题,这里就需要把我们创建的 jenkins 的 serviceAccount 对应的 secret 添加到这里的 Credentials 里面。

.

第3步. 配置 Pod Template,其实就是配置 Jenkins Slave 运行的 Pod 模板,命名空间我们同样是用 kube-ops,Labels 这里也非常重要,对于后面执行 Job 的时候需要用到该值,然后我们这里使用的是 cnych/jenkins:jnlp 这个镜像作为基础,加入了 maven 、kubectl等一些实用的工具,

Dockerfile

FROM cnych/jenkins:jnlp6COPY maven.zip /usr/local/COPY kubectl /usr/bin/RUN /   cd /usr/local/ && /   unzip -qo maven.zip && /   chmod -R 777 /usr/local/maven && /   chmod +x /usr/bin/kubectl && /   rm -rf /usr/local/bin/kubectl && /   ln -s /usr/local/maven/bin/mvn /usr/bin/mvn

修改mirror地址如下:

 <mirror>      <id>alimaven</id>      <name>aliyun maven</name>      <url>http://maven.aliyun.com/nexus/content/groups/public/</url>      <mirrorOf>central</mirrorOf>        </mirror>

目录结构如下:

[root@master maven]# tree.├── Dockerfile├── kubectl└── maven.zip0 directories, 3 files

打包镜像:

docker build -t yourtag/jenkins:jnlp6 .

下面Docker image改成yourtag/jenkins:jnlp6(下面图片显示cnych/jenkins:jnlp忽略不计)

.

注意:由于新版本的 Kubernetes 插件变化较多,如果你使用的 Jenkins 版本在 2.176.x 版本以上,注意将上面的镜像替换成yourtag/jenkins:jnlp6,否则使用会报错,配置如下图所示:

...kubernetes slave image config..

另外需要注意我们这里需要在下面挂载两个主机目录,一个是/var/run/docker.sock,该文件是用于 Pod 中的容器能够共享宿主机的 Docker,这就是大家说的 docker in docker 的方式,Docker 二进制文件我们已经打包到上面的镜像中了,另外一个目录下/root/.kube目录,我们将这个目录挂载到容器的/root/.kube目录下面这是为了让我们能够在 Pod 的容器中能够使用 kubectl 工具来访问我们的 Kubernetes 集群,方便我们后面在 Slave Pod 部署 Kubernetes 应用。 

挂载路径

/var/run/docker.sock/root/.kube/usr/bin/docker/usr/bin/kubectl#maven包/root/.m2/repository #宿主机路径/.m2/repository 


然后输入我们测试命令

echo "测试 Kubernetes 动态生成 jenkins slave"echo "==============docker in docker==========="docker infoecho "=============maven============="mvn -versionecho "=============kubectl============="kubectl get pods -n kube-ops

最后点击保存

现在我们直接在页面点击做成的 Build now 触发构建即可,然后观察 Kubernetes 集群中 Pod 的变化

$ kubectl get pods -n kube-opsNAME                       READY     STATUS              RESTARTS   AGEjenkins2-7c85b6f4bd-rfqgv   1/1       Running             3          1djnlp-hfmvd                 0/1       ContainerCreating   0          7s

我们可以看到在我们点击立刻构建的时候可以看到一个新的 Pod:jnlp-hfmvd 被创建了,这就是我们的 Jenkins Slave。任务执行完成后我们可以看到任务信息,比如我们这里是 花费了 5.2s 时间在 jnlp-hfmvd 这个 Slave上面

到这里证明我们的任务已经构建完成,然后这个时候我们再去集群查看我们的 Pod 列表,发现 kube-ops 这个 namespace 下面已经没有之前的 Slave 这个 Pod 了。

$ kubectl get pods -n kube-opsNAME                       READY     STATUS    RESTARTS   AGEjenkins2-7c85b6f4bd-rfqgv   1/1       Running   3          1d

到这里我们就完成了使用 Kubernetes 动态生成 Jenkins Slave 的方法。下节课我们来给大家介绍下怎么在 Jenkins 中来发布我们的 Kubernetes 应用。


.
.

广告 广告

评论区