前面几篇文章已经谈过在k8s中如何实现rook/ceph持久化存储和服务发现/负载均衡/服务暴露策略,接下来几篇文章将以springboot项目为例,详解如何利用kubernetes容器编排平台实现cicd流水线(devops)。
我们根据上面几篇文章已经有了一个k8s集群,常见的的cicd工具有jenkins,gitlab-ci和drone等等,但因为种种原因(比如gitlabci只支持gitlab,drone需要借助其他git仓库的用户权限系统不支持原生裸git),这里最终还是选择了jenkins进行实验。
首先要解决的肯定是流程上的问题,就是所谓的cicd流程,参考了一些演讲ppt的做法,不过ppt嘛,你懂的,图画的还是很好看,但是也啰里八嗦的,不够简洁明快,有些地方明明很简单却故意讲的很高深莫测好像很厉害的样子,总之就是看着看着就觉得有点好笑
大致流程图我就意思意思吧:
用户提交代码 ----> Jenkins触发构建 ----> 编译打包 ----> 归档成品 ----> 制作镜像 ---->k8s发布
其中构建触发这一部分因为是用的多分支流水线,觉得还是用触发扫描api由用户在运维平台点击一下比较好。构建因为是用的基于k8s的jenkins,所以同样用了动态的jenkins-slave,这样就可以做到有工作则自动生成jenkins-slave进行编译,无工作则自动销毁jenkins-slave释放资源,从而实现资源的最大化利用和伸缩性。
首先在k8s中部署jenkins
1. 创建jenkins-master-home pvc
# vim jenkins-master-pvc.yaml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: jenkins-master-home namespace: kube-ops labels: app: jenkins-master-home spec: storageClassName: rook-ceph-block accessModes: - ReadWriteOnce resources: requests: storage: 100Gi # kubectl apply -f jenkins-master-pvc.yaml
2. 创建 rbac
# vim rbac.yaml apiVersion: v1 kind: ServiceAccount metadata: name: jenkins2 namespace: kube-ops --- kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1beta1 metadata: name: jenkins2 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: jenkins2 roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: jenkins2 subjects: - kind: ServiceAccount name: jenkins2 namespace: kube-ops # kubectl apply -f rbac.yaml
3.创建jenkins deployment和service
# vim jenkins.yaml --- apiVersion: extensions/v1beta1 kind: Deployment metadata: name: jenkins2-deployment namespace: kube-ops spec: template: metadata: labels: app: jenkins2 spec: terminationGracePeriodSeconds: 10 serviceAccountName: 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: 4000m memory: 8Gi 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: jenkins-master-home --- apiVersion: v1 kind: Service metadata: name: jenkins2 namespace: kube-ops labels: app: jenkins2 spec: selector: app: jenkins2 type: ClusterIP ports: - name: web port: 8080 targetPort: web - name: agent port: 50000 targetPort: agent # kubectl apply -f jenkins.yaml
这个时候jenkins就已经起起来了,我们给jenkins的webui添加一个ingress负载均衡和服务暴露
# vim kube-ops-ingress.yaml apiVersion: extensions/v1beta1 kind: Ingress metadata: name: kube-ops-ingress annotations: nginx.ingress.kubernetes.io/ssl-redirect: "false" namespace: kube-ops spec: rules: - host: k8s-jenkins.example.cn http: paths: - path: / backend: serviceName: jenkins2 servicePort: 8080 # kubectl apply -f kube-ops-ingress.yaml
这时候因为ingress service是用的master node的externalIp,所以我们可以直接通过修改dns,将k8s-jenkins.example.cn域名指向master node的externalIp,然后直接访问k8s-jenkins.example.cn就可以访问到jenkins服务。(有问题可以查看下kubectl get ingress -o wide --all-namespaces)
然后安装常见的插件,进入jenkins后记得再安装如下两个插件
blueOcean(新一代的流水线UI),kubernetes(k8s slave支持),Multibranch Scan Webhook Trigger(多分枝流水线扫描触发器)
安装好之后,进入设置,拉到最下面,选择添加一个云(k8s)
按如上配置,在页面测试连接k8s正常即可(jenkins服务名这里叫jenkins2,按实际修改)
至于下面的是否添加镜像,我推荐是不在这里添加,而选择用Yaml直接编写添加。原因是,这里添加slave k8s pod tempalte的话,slave编号无法动态化,会导致后以后构建任务等待前任务,无法多slave并行。(很多文章会在这里添加slave pod template,然后用一个自由风格软件项目做例子,那根本无法在实际环境中用的)
这里还有个小坑,无论是yaml的slave k8s pod tempalte还是在jenkins设置里页面添加的slave k8s pod template,如果想使用自定义slave image的话,containers: - name 一定要填写成- name: jnlp,否则会不读取自定义slave image而采用官方的docker-jnlp-slave。
接下来我们创建自己的jenkins-jnlp-slave容器,参考官方的docker-jnlp-slave
Dockerfile如下
ARG VERSION=0.0.1 ARG user=jenkins ARG group=jenkins ARG uid=1000 ARG gid=1000 ENV HOME /home/${user} RUN groupadd -g ${gid} ${group} RUN useradd -d $HOME -u ${uid} -g ${group} ${user} LABEL maintainer="calmkart@calmkart.com" Description="jenkins jnlp slave image" Vendor="calmkart@calmkart.com" Version="${VERSION}" ARG AGENT_WORKDIR=/home/${user}/agent RUN curl --create-dirs -fsSLo /usr/share/jenkins/slave.jar https://repo.jenkins-ci.org/public/org/jenkins-ci/main/remoting/3.29/remoting-3.29.jar \ && chmod 755 /usr/share/jenkins \ && chmod 644 /usr/share/jenkins/slave.jar \ && rm -rf /etc/yum.repos.d/* COPY kubectl kubectl COPY jdk/ ./jdk COPY maven ./maven COPY jenkins-slave /usr/local/bin/jenkins-slave COPY CentOS-Base.repo /etc/yum.repos.d/ COPY epel.repo /etc/yum.repos.d/ RUN yum makecache \ && yum install -y unzip.x86_64 \ && chmod +x ./kubectl \ && chmod +x /usr/local/bin/jenkins-slave \ && mv ./kubectl /usr/local/bin/kubectl \ && /bin/bash maven/default/install_maven \ && /bin/bash jdk/default/install_jdk \ && yum install -y nodejs \ && yum install -y python36.x86_64 \ && yum install -y docker-ce.x86_64 \ && yum install -y git \ && yum install -y which USER ${user} ENV PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/maven/bin:/usr/java/jdk/bin RUN export PATH=$PATH:/opt/maven/bin:/usr/java/jdk/bin ENV AGENT_WORKDIR=${AGENT_WORKDIR} RUN mkdir /home/${user}/.jenkins && mkdir -p ${AGENT_WORKDIR} VOLUME /home/${user}/.jenkins VOLUME ${AGENT_WORKDIR} WORKDIR /home/${user} ENTRYPOINT ["jenkins-slave"]
这里创建的是一个基于centos的jenkins jnlp slave容器镜像,安装了python2,python3,nodejs,docker,kubectl,maven,git等工具和环境。
将该镜像build并上传到harbor仓库即可在项目的Jenkinsfile中使用.
以下是具体待构建项目中jenkins jnlp slave的pod template信息
# vim jnlp-slave-declarative.yaml metadata: namespace: kube-ops spec: containers: - name: jnlp image: harbor.example.org/example/jenkins-jnlp-slave:v0.0.1 workingDir: /home/jenkins ttyEnabled: true privileged: false alwaysPullImage: false volumeMounts: - name: volume-0 mountPath: /var/run/docker.sock - name: volume-1 mountPath: /home/jenkins/.kube - name: volume-2 mountPath: /home/jenkins - name: volume-3 mountPath: /root/.m2 volumes: - name: volume-0 hostPath: path: /var/run/docker.sock type: "" - name: volume-1 hostPath: path: /root/.kube type: "" - name: volume-2 nfs: path: /home/shared/nfs/jenkins-home server: 10.1.33.159 - name: volume-3 nfs: path: /home/shared/nfs/maven-home server: 10.1.33.159
关于这个slave k8s pod template要解释一些东西,这里挂载了4个volume到slave中,分别是
1.在slave中调用宿主机的docker命令,用于构造和上传镜像
/var/run/docker.sock -> /var/run/docker.sock
2.在slave中调用宿主机的kubectl命令,用于在k8s中发布项目(创建deployment和service)
/root/.kube -> /home/jenkins/.kube
3.搭建了一个nfs用于多个slave共享workspace,避免多次clone代码
/home/shared/nfs/jenkins-home -> /home/jenkins
4.搭建了一个nfs用于多个slave共享本地maven仓库,避免多次下载jar包,同时统一maven配置
/home/shared/nfs/maven-home —> /root/.m2
这里为什么不用ceph呢?原因很简单,因为ceph rbd块存储不支持ReadWriteMany,而cephfs共享存储虽然支持ReadWriteMany但是不支持分区。所以还是采用了最简单的nfs实现共享存储。
在项目的Jenkinsfile中编写如下agent字段调用该pod template的slave
def label_tag = "slave-${UUID.randomUUID().toString()}" pipeline { agent { kubernetes { label label_tag yamlFile 'jnlp-slave-declarative.yaml' } } } }
这是申明式pipeline的用法中比较好的写法,因为如果不单独写一个yaml的话我记得是不支持很多细节语法的.(具体细节参考jenkins kubernetes plugin插件说明)
做到这里之后,我们扫描多分支流水线,就可以发现创建一个新slave处理构建任务,构建完成则自动销毁了。
但这还远远不够,很多细节没有处理,下篇文章再详述编译发布构成和申明式Pipeline Jenkinsfile,以及项目Dockerfile镜像详细写法例子。
大神,我知道CI是持续集成,CD是什么啊?
continuous integration continuous deployment
楼主最近看机会吗,我们组正在招聘运维开发工程师,主要负责公司发布系统和监控系统的开发和维护,公司叫省钱快报,办公地点在北二环,地铁5号线雍和宫站附近,薪资待遇业界上游水平,期待您的回复。
不好意思,年内没有换工作的意向.